@teambit/ci 1.0.505 → 1.0.508
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ci.main.runtime.d.ts +3 -2
- package/dist/ci.main.runtime.js +5 -6
- package/dist/ci.main.runtime.js.map +1 -1
- package/dist/{preview-1787074471717.js → preview-1787160027560.js} +1 -1
- package/dist/sync/git-host-provider.d.ts +9 -0
- package/dist/sync/git-host-provider.js.map +1 -1
- package/dist/sync/github-client.d.ts +28 -0
- package/dist/sync/github-client.js +81 -3
- package/dist/sync/github-client.js.map +1 -1
- package/dist/sync/github-client.spec.js +249 -0
- package/dist/sync/github-client.spec.js.map +1 -1
- package/dist/sync/lane-sync-executor.d.ts +47 -3
- package/dist/sync/lane-sync-executor.js +191 -39
- package/dist/sync/lane-sync-executor.js.map +1 -1
- package/dist/sync/lane-sync-executor.spec.js +336 -0
- package/dist/sync/lane-sync-executor.spec.js.map +1 -1
- package/dist/sync/sync-planner.d.ts +12 -2
- package/dist/sync/sync-planner.js +17 -8
- package/dist/sync/sync-planner.js.map +1 -1
- package/dist/sync/sync-planner.spec.js +64 -0
- package/dist/sync/sync-planner.spec.js.map +1 -1
- package/dist/sync/sync-state.d.ts +34 -3
- package/dist/sync/sync-state.js +61 -3
- package/dist/sync/sync-state.js.map +1 -1
- package/dist/sync/sync-state.spec.js +54 -0
- package/dist/sync/sync-state.spec.js.map +1 -1
- package/package.json +15 -15
- package/sync/git-host-provider.ts +8 -0
- package/sync/github-client.spec.ts +160 -0
- package/sync/github-client.ts +90 -3
- package/sync/lane-sync-executor.spec.ts +291 -0
- package/sync/lane-sync-executor.ts +233 -50
- package/sync/sync-planner.spec.ts +41 -0
- package/sync/sync-planner.ts +31 -10
- package/sync/sync-state.spec.ts +48 -0
- package/sync/sync-state.ts +104 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_chai","data","require","_githubClient","REMOTES","undefined","describe","it","forEach","url","repo","isGitHub","expect","parseGitHubRepo","to","equal","isGitHubRemote","envKeys","saved","beforeEach","key","process","env","GITHUB_TOKEN","afterEach","OURS","RESOLUTION","name","repository","remote","warns","GITHUB_REPOSITORY","warnings","client","GitHubClient","fromEnv","message","push","fragment","join","contain","deep","realFetch","calls","globalThis","fetch","init","String","Response","JSON","stringify","status","headers","sentToken","findPrByBranch","authorization","PRECEDENCE","BIT_GITHUB_TOKEN","expected","Object","entries","value","have","lengthOf","fakeFetch","number","state","labels","head","ref","html_url","token","fetchImpl","pr","createPr","base","title","body","method","parse"],"sources":["github-client.spec.ts"],"sourcesContent":["import { expect } from 'chai';\nimport { GitHubClient, isGitHubRemote, parseGitHubRepo } from './github-client';\n\n/**\n * A claimed remote the parse then fails on silently degrades the whole run to PR-less mode, so the two\n * predicates are pinned over the same remote shapes: whatever `isGitHubRemote` claims must parse.\n */\nconst REMOTES: Array<[string, string | undefined, boolean]> = [\n // url, parseGitHubRepo, isGitHubRemote\n ['git@github.com:acme/shop.git', 'acme/shop', true],\n ['https://github.com/acme/shop.git', 'acme/shop', true],\n ['https://github.com/acme/shop', 'acme/shop', true],\n ['ssh://git@github.com/acme/shop.git', 'acme/shop', true],\n // mixed-case host, an explicit port, and credentials in the url are all still github.com\n ['https://GitHub.com/acme/shop.git', 'acme/shop', true],\n ['https://github.com:443/acme/shop.git', 'acme/shop', true],\n ['ssh://git@github.com:22/acme/shop.git', 'acme/shop', true],\n ['https://x-access-token:tok@github.com/acme/shop.git', 'acme/shop', true],\n // an all-digits scp owner is an owner, never a port\n ['git@github.com:12345/shop.git', '12345/shop', true],\n // another host that merely has \"github.com\" in its path, in both url forms\n ['https://gitlab.example.com/mirrors/github.com/acme/repo.git', undefined, false],\n ['git@gitlab.example.com:mirrors/github.com/acme/repo.git', undefined, false],\n // look-alike and unrelated hosts\n ['https://mygithub.com/acme/shop', undefined, false],\n ['https://github.company.com/acme/shop', undefined, false],\n ['git@gitlab.com:acme/shop.git', undefined, false],\n ['https://gitlab.com/acme/shop.git', undefined, false],\n ['/srv/git/github.com/acme/shop.git', undefined, false],\n];\n\ndescribe('parseGitHubRepo / isGitHubRemote', () => {\n it('agree on every remote form git accepts, and on every host that is not github.com', () => {\n REMOTES.forEach(([url, repo, isGitHub]) => {\n expect(parseGitHubRepo(url), url).to.equal(repo);\n expect(isGitHubRemote(url), url).to.equal(isGitHub);\n });\n });\n});\n\ndescribe('GitHubClient.fromEnv', () => {\n const envKeys = ['GITHUB_TOKEN', 'BIT_GITHUB_TOKEN', 'GITHUB_REPOSITORY'] as const;\n const saved: Record<string, string | undefined> = {};\n\n beforeEach(() => {\n envKeys.forEach((key) => {\n saved[key] = process.env[key];\n delete process.env[key];\n });\n process.env.GITHUB_TOKEN = 'tok';\n });\n\n afterEach(() => {\n envKeys.forEach((key) => {\n if (saved[key] === undefined) delete process.env[key];\n else process.env[key] = saved[key];\n });\n });\n\n const OURS = 'git@github.com:acme/shop.git';\n /** [case, GITHUB_REPOSITORY, remote, resolved repo, warning fragments]. */\n const RESOLUTION: Array<[string, string, string | undefined, string | undefined, string[]?]> = [\n ['the remote and the environment agree', 'acme/shop', OURS, 'acme/shop'],\n // the ORIGIN-parsed repository outranks GITHUB_REPOSITORY, and the disagreement must be reported\n ['they disagree', 'other-org/other-repo', OURS, 'acme/shop', ['other-org/other-repo', 'acme/shop']],\n // GitHub repository names are case-insensitive, so a case-only difference is not a disagreement\n ['they differ only in case', 'Acme/Shop', OURS, 'acme/shop'],\n ['there is no remote to parse', 'acme/shop', undefined, 'acme/shop'],\n ['the remote is a github url nothing parses out of', 'acme/shop', 'https://github.com/', 'acme/shop'],\n // a remote that is demonstrably not github stays unconfigured however complete the environment is\n ['the remote is another host', 'acme/shop', 'https://gitlab.com/acme/shop.git', undefined],\n ['the remote is a look-alike host', 'acme/shop', 'https://mygithub.com/acme/shop', undefined],\n ];\n\n RESOLUTION.forEach(([name, repository, remote, repo, warns]) => {\n it(`resolves the repository when ${name}`, () => {\n process.env.GITHUB_REPOSITORY = repository;\n const warnings: string[] = [];\n const client = GitHubClient.fromEnv(remote, (message) => warnings.push(message));\n expect(client?.repo).to.equal(repo);\n (warns ?? []).forEach((fragment) => expect(warnings.join('\\n')).to.contain(fragment));\n if (!warns) expect(warnings).to.deep.equal([]);\n });\n });\n\n it('needs a token: a repository on its own configures nothing', () => {\n delete process.env.GITHUB_TOKEN;\n process.env.GITHUB_REPOSITORY = 'acme/shop';\n expect(GitHubClient.fromEnv('git@github.com:acme/shop.git')).to.equal(undefined);\n });\n\n // Asserted through the `authorization` header, so what is pinned is the token the client actually\n // sends. The fetch stub must be installed before `fromEnv` runs.\n describe('token precedence', () => {\n let realFetch: typeof fetch;\n let calls: Array<{ url: string; init: any }>;\n\n beforeEach(() => {\n realFetch = globalThis.fetch;\n calls = [];\n globalThis.fetch = (async (url: any, init: any) => {\n calls.push({ url: String(url), init });\n return new Response(JSON.stringify([]), { status: 200, headers: { 'content-type': 'application/json' } });\n }) as typeof fetch;\n });\n\n afterEach(() => {\n globalThis.fetch = realFetch;\n });\n\n /** The token a client built from this environment puts on the wire. */\n async function sentToken(): Promise<string | undefined> {\n const client = GitHubClient.fromEnv('git@github.com:acme/shop.git');\n await client?.findPrByBranch('lane-x');\n return calls[0]?.init?.headers?.authorization;\n }\n\n const PRECEDENCE: Array<[string, Record<string, string | undefined>, string]> = [\n // the override beats the ambient default\n ['both set', { GITHUB_TOKEN: 'workflow-token', BIT_GITHUB_TOKEN: 'user-pat' }, 'Bearer user-pat'],\n // the ordinary Actions job\n ['only GITHUB_TOKEN', { GITHUB_TOKEN: 'workflow-token' }, 'Bearer workflow-token'],\n // any CI that is not Actions\n ['only BIT_GITHUB_TOKEN', { BIT_GITHUB_TOKEN: 'user-pat' }, 'Bearer user-pat'],\n ['an EMPTY override', { GITHUB_TOKEN: 'workflow-token', BIT_GITHUB_TOKEN: '' }, 'Bearer workflow-token'],\n ];\n\n PRECEDENCE.forEach(([name, env, expected]) => {\n it(`sends the right token with ${name}`, async () => {\n delete process.env.GITHUB_TOKEN;\n Object.entries(env).forEach(([key, value]) => {\n process.env[key] = value;\n });\n expect(await sentToken()).to.equal(expected);\n });\n });\n\n it('is not configured when neither token is set, and puts nothing on the wire', async () => {\n delete process.env.GITHUB_TOKEN;\n process.env.GITHUB_REPOSITORY = 'acme/shop';\n expect(GitHubClient.fromEnv('git@github.com:acme/shop.git')).to.equal(undefined);\n expect(calls).to.have.lengthOf(0);\n });\n });\n});\n\ndescribe('GitHubClient', () => {\n it('createPr posts the pull-request payload to the pulls endpoint', async () => {\n const calls: Array<{ url: string; init: any }> = [];\n const fakeFetch = (async (url: any, init: any) => {\n calls.push({ url: String(url), init });\n return new Response(\n JSON.stringify({ number: 7, state: 'open', labels: [], head: { ref: 'lane-x' }, html_url: 'http://pr/7' }),\n { status: 201, headers: { 'content-type': 'application/json' } }\n );\n }) as typeof fetch;\n const client = new GitHubClient({ token: 'tok', repo: 'acme/shop', fetchImpl: fakeFetch });\n const pr = await client.createPr({ head: 'lane-x', base: 'main', title: 't', body: 'b' });\n expect(pr.number).to.equal(7);\n expect(calls[0].url).to.equal('https://api.github.com/repos/acme/shop/pulls');\n expect(calls[0].init.method).to.equal('POST');\n expect(JSON.parse(calls[0].init.body)).to.deep.equal({ head: 'lane-x', base: 'main', title: 't', body: 'b' });\n });\n\n it('findPrByBranch returns undefined on empty list', async () => {\n const fakeFetch = (async () =>\n new Response(JSON.stringify([]), {\n status: 200,\n headers: { 'content-type': 'application/json' },\n })) as typeof fetch;\n const client = new GitHubClient({ token: 'tok', repo: 'acme/shop', fetchImpl: fakeFetch });\n expect(await client.findPrByBranch('lane-x')).to.equal(undefined);\n });\n});\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":["_chai","data","require","_githubClient","REMOTES","undefined","describe","it","forEach","url","repo","isGitHub","expect","parseGitHubRepo","to","equal","isGitHubRemote","envKeys","saved","beforeEach","key","process","env","GITHUB_TOKEN","afterEach","OURS","RESOLUTION","name","repository","remote","warns","GITHUB_REPOSITORY","warnings","client","GitHubClient","fromEnv","message","push","fragment","join","contain","deep","realFetch","calls","globalThis","fetch","init","String","Response","JSON","stringify","status","headers","sentToken","findPrByBranch","authorization","PRECEDENCE","BIT_GITHUB_TOKEN","expected","Object","entries","value","have","lengthOf","fakeFetch","number","state","labels","head","ref","html_url","token","fetchImpl","pr","createPr","base","title","body","method","parse","fakeFetchOver","existingComments","upsertComment","posts","filter","c","id","patches","some","createIfAbsent","includes","link","gets","include","thrown","catch","e","be","an"],"sources":["github-client.spec.ts"],"sourcesContent":["import { expect } from 'chai';\nimport { GitHubClient, isGitHubRemote, parseGitHubRepo } from './github-client';\n\n/**\n * A claimed remote the parse then fails on silently degrades the whole run to PR-less mode, so the two\n * predicates are pinned over the same remote shapes: whatever `isGitHubRemote` claims must parse.\n */\nconst REMOTES: Array<[string, string | undefined, boolean]> = [\n // url, parseGitHubRepo, isGitHubRemote\n ['git@github.com:acme/shop.git', 'acme/shop', true],\n ['https://github.com/acme/shop.git', 'acme/shop', true],\n ['https://github.com/acme/shop', 'acme/shop', true],\n ['ssh://git@github.com/acme/shop.git', 'acme/shop', true],\n // mixed-case host, an explicit port, and credentials in the url are all still github.com\n ['https://GitHub.com/acme/shop.git', 'acme/shop', true],\n ['https://github.com:443/acme/shop.git', 'acme/shop', true],\n ['ssh://git@github.com:22/acme/shop.git', 'acme/shop', true],\n ['https://x-access-token:tok@github.com/acme/shop.git', 'acme/shop', true],\n // an all-digits scp owner is an owner, never a port\n ['git@github.com:12345/shop.git', '12345/shop', true],\n // another host that merely has \"github.com\" in its path, in both url forms\n ['https://gitlab.example.com/mirrors/github.com/acme/repo.git', undefined, false],\n ['git@gitlab.example.com:mirrors/github.com/acme/repo.git', undefined, false],\n // look-alike and unrelated hosts\n ['https://mygithub.com/acme/shop', undefined, false],\n ['https://github.company.com/acme/shop', undefined, false],\n ['git@gitlab.com:acme/shop.git', undefined, false],\n ['https://gitlab.com/acme/shop.git', undefined, false],\n ['/srv/git/github.com/acme/shop.git', undefined, false],\n];\n\ndescribe('parseGitHubRepo / isGitHubRemote', () => {\n it('agree on every remote form git accepts, and on every host that is not github.com', () => {\n REMOTES.forEach(([url, repo, isGitHub]) => {\n expect(parseGitHubRepo(url), url).to.equal(repo);\n expect(isGitHubRemote(url), url).to.equal(isGitHub);\n });\n });\n});\n\ndescribe('GitHubClient.fromEnv', () => {\n const envKeys = ['GITHUB_TOKEN', 'BIT_GITHUB_TOKEN', 'GITHUB_REPOSITORY'] as const;\n const saved: Record<string, string | undefined> = {};\n\n beforeEach(() => {\n envKeys.forEach((key) => {\n saved[key] = process.env[key];\n delete process.env[key];\n });\n process.env.GITHUB_TOKEN = 'tok';\n });\n\n afterEach(() => {\n envKeys.forEach((key) => {\n if (saved[key] === undefined) delete process.env[key];\n else process.env[key] = saved[key];\n });\n });\n\n const OURS = 'git@github.com:acme/shop.git';\n /** [case, GITHUB_REPOSITORY, remote, resolved repo, warning fragments]. */\n const RESOLUTION: Array<[string, string, string | undefined, string | undefined, string[]?]> = [\n ['the remote and the environment agree', 'acme/shop', OURS, 'acme/shop'],\n // the ORIGIN-parsed repository outranks GITHUB_REPOSITORY, and the disagreement must be reported\n ['they disagree', 'other-org/other-repo', OURS, 'acme/shop', ['other-org/other-repo', 'acme/shop']],\n // GitHub repository names are case-insensitive, so a case-only difference is not a disagreement\n ['they differ only in case', 'Acme/Shop', OURS, 'acme/shop'],\n ['there is no remote to parse', 'acme/shop', undefined, 'acme/shop'],\n ['the remote is a github url nothing parses out of', 'acme/shop', 'https://github.com/', 'acme/shop'],\n // a remote that is demonstrably not github stays unconfigured however complete the environment is\n ['the remote is another host', 'acme/shop', 'https://gitlab.com/acme/shop.git', undefined],\n ['the remote is a look-alike host', 'acme/shop', 'https://mygithub.com/acme/shop', undefined],\n ];\n\n RESOLUTION.forEach(([name, repository, remote, repo, warns]) => {\n it(`resolves the repository when ${name}`, () => {\n process.env.GITHUB_REPOSITORY = repository;\n const warnings: string[] = [];\n const client = GitHubClient.fromEnv(remote, (message) => warnings.push(message));\n expect(client?.repo).to.equal(repo);\n (warns ?? []).forEach((fragment) => expect(warnings.join('\\n')).to.contain(fragment));\n if (!warns) expect(warnings).to.deep.equal([]);\n });\n });\n\n it('needs a token: a repository on its own configures nothing', () => {\n delete process.env.GITHUB_TOKEN;\n process.env.GITHUB_REPOSITORY = 'acme/shop';\n expect(GitHubClient.fromEnv('git@github.com:acme/shop.git')).to.equal(undefined);\n });\n\n // Asserted through the `authorization` header, so what is pinned is the token the client actually\n // sends. The fetch stub must be installed before `fromEnv` runs.\n describe('token precedence', () => {\n let realFetch: typeof fetch;\n let calls: Array<{ url: string; init: any }>;\n\n beforeEach(() => {\n realFetch = globalThis.fetch;\n calls = [];\n globalThis.fetch = (async (url: any, init: any) => {\n calls.push({ url: String(url), init });\n return new Response(JSON.stringify([]), { status: 200, headers: { 'content-type': 'application/json' } });\n }) as typeof fetch;\n });\n\n afterEach(() => {\n globalThis.fetch = realFetch;\n });\n\n /** The token a client built from this environment puts on the wire. */\n async function sentToken(): Promise<string | undefined> {\n const client = GitHubClient.fromEnv('git@github.com:acme/shop.git');\n await client?.findPrByBranch('lane-x');\n return calls[0]?.init?.headers?.authorization;\n }\n\n const PRECEDENCE: Array<[string, Record<string, string | undefined>, string]> = [\n // the override beats the ambient default\n ['both set', { GITHUB_TOKEN: 'workflow-token', BIT_GITHUB_TOKEN: 'user-pat' }, 'Bearer user-pat'],\n // the ordinary Actions job\n ['only GITHUB_TOKEN', { GITHUB_TOKEN: 'workflow-token' }, 'Bearer workflow-token'],\n // any CI that is not Actions\n ['only BIT_GITHUB_TOKEN', { BIT_GITHUB_TOKEN: 'user-pat' }, 'Bearer user-pat'],\n ['an EMPTY override', { GITHUB_TOKEN: 'workflow-token', BIT_GITHUB_TOKEN: '' }, 'Bearer workflow-token'],\n ];\n\n PRECEDENCE.forEach(([name, env, expected]) => {\n it(`sends the right token with ${name}`, async () => {\n delete process.env.GITHUB_TOKEN;\n Object.entries(env).forEach(([key, value]) => {\n process.env[key] = value;\n });\n expect(await sentToken()).to.equal(expected);\n });\n });\n\n it('is not configured when neither token is set, and puts nothing on the wire', async () => {\n delete process.env.GITHUB_TOKEN;\n process.env.GITHUB_REPOSITORY = 'acme/shop';\n expect(GitHubClient.fromEnv('git@github.com:acme/shop.git')).to.equal(undefined);\n expect(calls).to.have.lengthOf(0);\n });\n });\n});\n\ndescribe('GitHubClient', () => {\n it('createPr posts the pull-request payload to the pulls endpoint', async () => {\n const calls: Array<{ url: string; init: any }> = [];\n const fakeFetch = (async (url: any, init: any) => {\n calls.push({ url: String(url), init });\n return new Response(\n JSON.stringify({ number: 7, state: 'open', labels: [], head: { ref: 'lane-x' }, html_url: 'http://pr/7' }),\n { status: 201, headers: { 'content-type': 'application/json' } }\n );\n }) as typeof fetch;\n const client = new GitHubClient({ token: 'tok', repo: 'acme/shop', fetchImpl: fakeFetch });\n const pr = await client.createPr({ head: 'lane-x', base: 'main', title: 't', body: 'b' });\n expect(pr.number).to.equal(7);\n expect(calls[0].url).to.equal('https://api.github.com/repos/acme/shop/pulls');\n expect(calls[0].init.method).to.equal('POST');\n expect(JSON.parse(calls[0].init.body)).to.deep.equal({ head: 'lane-x', base: 'main', title: 't', body: 'b' });\n });\n\n it('findPrByBranch returns undefined on empty list', async () => {\n const fakeFetch = (async () =>\n new Response(JSON.stringify([]), {\n status: 200,\n headers: { 'content-type': 'application/json' },\n })) as typeof fetch;\n const client = new GitHubClient({ token: 'tok', repo: 'acme/shop', fetchImpl: fakeFetch });\n expect(await client.findPrByBranch('lane-x')).to.equal(undefined);\n });\n\n describe('upsertComment', () => {\n // named `upsertComment`, matching `GitHostProvider.upsertComment` exactly — see the method's\n // own doc comment for why a differently-named method would be a silent-no-op trap.\n function fakeFetchOver(existingComments: Array<{ id: number; body: string }>) {\n const calls: Array<{ url: string; init: any }> = [];\n const fetchImpl = (async (url: any, init: any) => {\n calls.push({ url: String(url), init });\n const method = init?.method ?? 'GET';\n if (method === 'GET') {\n return new Response(JSON.stringify(existingComments), {\n status: 200,\n headers: { 'content-type': 'application/json' },\n });\n }\n return new Response(JSON.stringify({}), { status: 200, headers: { 'content-type': 'application/json' } });\n }) as typeof fetch;\n return { calls, fetchImpl };\n }\n\n it('posts a new comment when no marked comment exists', async () => {\n const { calls, fetchImpl } = fakeFetchOver([]);\n const client = new GitHubClient({ token: 'tok', repo: 'acme/shop', fetchImpl });\n await client.upsertComment(7, '<!-- marker -->', '<!-- marker -->\\nbody');\n const posts = calls.filter((c) => c.init?.method === 'POST');\n expect(posts).to.have.lengthOf(1);\n expect(posts[0].url).to.equal('https://api.github.com/repos/acme/shop/issues/7/comments');\n });\n\n it('patches the marked comment in place, and never posts a second one', async () => {\n const { calls, fetchImpl } = fakeFetchOver([\n { id: 42, body: 'unrelated comment' },\n { id: 99, body: '<!-- marker -->\\nold report' },\n ]);\n const client = new GitHubClient({ token: 'tok', repo: 'acme/shop', fetchImpl });\n await client.upsertComment(7, '<!-- marker -->', '<!-- marker -->\\nnew report');\n const patches = calls.filter((c) => c.init?.method === 'PATCH');\n expect(patches).to.have.lengthOf(1);\n expect(patches[0].url).to.equal('https://api.github.com/repos/acme/shop/issues/comments/99');\n expect(JSON.parse(patches[0].init.body)).to.deep.equal({ body: '<!-- marker -->\\nnew report' });\n expect(calls.some((c) => c.init?.method === 'POST')).to.equal(false);\n });\n\n it('skips silently when createIfAbsent is false and no marked comment exists', async () => {\n const { calls, fetchImpl } = fakeFetchOver([]);\n const client = new GitHubClient({ token: 'tok', repo: 'acme/shop', fetchImpl });\n await client.upsertComment(7, '<!-- marker -->', 'cleared', { createIfAbsent: false });\n expect(calls.some((c) => c.init?.method === 'POST' || c.init?.method === 'PATCH')).to.equal(false);\n });\n\n it('lists at per_page=100 and follows Link-header pagination to find a comment past page 1', async () => {\n // GitHub defaults to per_page=30; a naive single-page list would miss this comment and post a\n // duplicate report on every push instead of updating it.\n const calls: Array<{ url: string; init: any }> = [];\n const fetchImpl = (async (url: any, init: any) => {\n calls.push({ url: String(url), init });\n const method = init?.method ?? 'GET';\n if (method === 'GET' && !String(url).includes('page=2')) {\n return new Response(JSON.stringify([{ id: 1, body: 'unrelated' }]), {\n status: 200,\n headers: {\n 'content-type': 'application/json',\n link: '<https://api.github.com/repos/acme/shop/issues/7/comments?per_page=100&page=2>; rel=\"next\"',\n },\n });\n }\n if (method === 'GET') {\n return new Response(JSON.stringify([{ id: 99, body: '<!-- marker -->\\nold report' }]), {\n status: 200,\n headers: { 'content-type': 'application/json' },\n });\n }\n return new Response(JSON.stringify({}), { status: 200, headers: { 'content-type': 'application/json' } });\n }) as typeof fetch;\n const client = new GitHubClient({ token: 'tok', repo: 'acme/shop', fetchImpl });\n await client.upsertComment(7, '<!-- marker -->', '<!-- marker -->\\nnew report');\n\n const gets = calls.filter((c) => (c.init?.method ?? 'GET') === 'GET');\n expect(gets).to.have.lengthOf(2);\n expect(gets[0].url).to.include('per_page=100');\n expect(gets[1].url).to.equal('https://api.github.com/repos/acme/shop/issues/7/comments?per_page=100&page=2');\n const patches = calls.filter((c) => c.init?.method === 'PATCH');\n expect(patches).to.have.lengthOf(1);\n expect(patches[0].url).to.equal('https://api.github.com/repos/acme/shop/issues/comments/99');\n // the found-on-page-2 comment was updated, not duplicated\n expect(calls.some((c) => c.init?.method === 'POST')).to.equal(false);\n });\n\n it('refuses to follow a Link header that leaves api.github.com — the token stays home', async () => {\n // The next-page URL is response data, and requestRaw attaches the bearer token to whatever URL\n // it is handed — an off-host \"next\" link must read as \"last page\", never as a request target.\n const calls: Array<{ url: string; init: any }> = [];\n const fetchImpl = (async (url: any, init: any) => {\n calls.push({ url: String(url), init });\n const method = init?.method ?? 'GET';\n if (method === 'GET') {\n return new Response(JSON.stringify([{ id: 1, body: 'unrelated' }]), {\n status: 200,\n headers: {\n 'content-type': 'application/json',\n link: '<https://evil.test/repos/acme/shop/issues/7/comments?page=2>; rel=\"next\"',\n },\n });\n }\n return new Response(JSON.stringify({}), { status: 200, headers: { 'content-type': 'application/json' } });\n }) as typeof fetch;\n const client = new GitHubClient({ token: 'tok', repo: 'acme/shop', fetchImpl });\n await client.upsertComment(7, '<!-- marker -->', '<!-- marker -->\\nbody');\n\n expect(calls.some((c) => c.url.includes('evil.test'))).to.equal(false);\n const gets = calls.filter((c) => (c.init?.method ?? 'GET') === 'GET');\n expect(gets, 'the off-host link is the last page, not a loop').to.have.lengthOf(1);\n });\n\n it('stops paginating as soon as the marked comment is seen', async () => {\n const calls: Array<{ url: string; init: any }> = [];\n const fetchImpl = (async (url: any, init: any) => {\n calls.push({ url: String(url), init });\n const method = init?.method ?? 'GET';\n if (method === 'GET') {\n return new Response(JSON.stringify([{ id: 99, body: '<!-- marker -->\\nold report' }]), {\n status: 200,\n headers: {\n 'content-type': 'application/json',\n link: '<https://api.github.com/repos/acme/shop/issues/7/comments?per_page=100&page=2>; rel=\"next\"',\n },\n });\n }\n return new Response(JSON.stringify({}), { status: 200, headers: { 'content-type': 'application/json' } });\n }) as typeof fetch;\n const client = new GitHubClient({ token: 'tok', repo: 'acme/shop', fetchImpl });\n await client.upsertComment(7, '<!-- marker -->', '<!-- marker -->\\nnew report');\n\n const gets = calls.filter((c) => (c.init?.method ?? 'GET') === 'GET');\n expect(gets, 'the match on page 1 makes page 2 pointless').to.have.lengthOf(1);\n expect(calls.filter((c) => c.init?.method === 'PATCH')).to.have.lengthOf(1);\n });\n\n it('exhausting the page budget throws instead of posting a duplicate', async () => {\n // \"Ran out of budget\" must not read as \"absent\": a POST here would add a duplicate report on\n // every later push. The throw surfaces through the caller's existing warn-and-skip path.\n const calls: Array<{ url: string; init: any }> = [];\n const fetchImpl = (async (url: any, init: any) => {\n calls.push({ url: String(url), init });\n return new Response(JSON.stringify([{ id: 1, body: 'unrelated' }]), {\n status: 200,\n headers: {\n 'content-type': 'application/json',\n link: '<https://api.github.com/repos/acme/shop/issues/7/comments?per_page=100&page=2>; rel=\"next\"',\n },\n });\n }) as typeof fetch;\n const client = new GitHubClient({ token: 'tok', repo: 'acme/shop', fetchImpl });\n let thrown: Error | undefined;\n await client.upsertComment(7, '<!-- marker -->', '<!-- marker -->\\nbody').catch((e) => (thrown = e));\n\n expect(thrown, 'the budget exhaustion is an error, not an absence').to.be.an('error');\n expect(calls.some((c) => c.init?.method === 'POST')).to.equal(false);\n });\n });\n});\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -32,6 +32,11 @@ export declare function racedLedgerPushSummary(laneName: string, policyClause?:
|
|
|
32
32
|
* run re-plans first contact against the winner's tip.
|
|
33
33
|
*/
|
|
34
34
|
export declare function racedAdoptionPushSummary(laneName: string): string;
|
|
35
|
+
/**
|
|
36
|
+
* An HTML comment embedded in the run-summary comment's body, so `upsertComment` finds and replaces
|
|
37
|
+
* THIS comment on a later run rather than posting a duplicate summary on every push.
|
|
38
|
+
*/
|
|
39
|
+
export declare const RUN_SUMMARY_MARKER = "<!-- bit-ci-sync:run-summary -->";
|
|
35
40
|
export type LaneSyncDeps = {
|
|
36
41
|
lanes: LanesMain;
|
|
37
42
|
/** for snapPrCommit + getDefaultBranchName + switchToLaneForSync */
|
|
@@ -133,8 +138,12 @@ export declare class LaneSyncExecutor {
|
|
|
133
138
|
*/
|
|
134
139
|
private executeImportLane;
|
|
135
140
|
/**
|
|
136
|
-
* Push the branch's dev commits back onto the lane
|
|
137
|
-
*
|
|
141
|
+
* Push the branch's dev commits back onto the lane. Probe first, mutate second: like adoption, land
|
|
142
|
+
* the lane in the workspace keeping the BRANCH's files, then ask `bit status` whether the branch's
|
|
143
|
+
* tree holds anything the lane is missing. Nothing → converged, and nothing is written or pushed —
|
|
144
|
+
* which is why a suspected-work plan can repeat run after run without a memo, and why no commit
|
|
145
|
+
* message is ever consulted to suppress it. Something → snap + export, then commit the resulting
|
|
146
|
+
* `.bitmap` back onto the branch so the next run reads the two sides as converged.
|
|
138
147
|
*/
|
|
139
148
|
private executeExportBranch;
|
|
140
149
|
/**
|
|
@@ -170,7 +179,8 @@ export declare class LaneSyncExecutor {
|
|
|
170
179
|
* so any earlier fingerprint is stale), commit — crucially the `.bitmap` the snap rewrote — and push.
|
|
171
180
|
* `unreadable` means the lane could no longer be read back, in which case the caller halts;
|
|
172
181
|
* `raced` means a concurrent run's push already got there first, in which case the caller reports
|
|
173
|
-
* a benign noop instead. `adopted` adds the audit-only `ADOPTION_TRAILER`.
|
|
182
|
+
* a benign noop instead. `adopted` adds the audit-only `ADOPTION_TRAILER`. The re-queried lane is
|
|
183
|
+
* also the caller's only cheap source of POST-export component versions.
|
|
174
184
|
*/
|
|
175
185
|
private recordLaneHeadOnBranch;
|
|
176
186
|
/**
|
|
@@ -264,6 +274,15 @@ export declare class LaneSyncExecutor {
|
|
|
264
274
|
* failing the lane — the git side of the sync is still correct and the next run re-reads the PR.
|
|
265
275
|
*/
|
|
266
276
|
private findPr;
|
|
277
|
+
/**
|
|
278
|
+
* Post (or update) ONE maintained comment on the branch's PR: what this export did to the scope —
|
|
279
|
+
* the components it snapped and the synced branch/lane anchors — information the branch's git diff
|
|
280
|
+
* cannot show, since the snap step alone can move dependency ranges the PR's files never touched.
|
|
281
|
+
* Best-effort throughout: no configured git host, no open PR yet, or a provider that doesn't
|
|
282
|
+
* implement `upsertComment` are all normal states, not failures — the git/lane pair already
|
|
283
|
+
* converged without this comment. A comment API error warns and never fails the run.
|
|
284
|
+
*/
|
|
285
|
+
private postRunSummaryComment;
|
|
267
286
|
/**
|
|
268
287
|
* The subject of the newest commit on the branch that isn't one of our own sync commits — it's
|
|
269
288
|
* the developer's own description of the change, and becomes the snap message on the lane.
|
|
@@ -310,6 +329,31 @@ export declare function laneSyncPrBody({ laneIdStr, laneUrl, branch, laneHead, c
|
|
|
310
329
|
laneHead: string;
|
|
311
330
|
components: LaneData['components'];
|
|
312
331
|
}): string;
|
|
332
|
+
/**
|
|
333
|
+
* The lane components this export actually changed: new on the lane, or moved to a new head. Diffed
|
|
334
|
+
* by id against the lane's content BEFORE the export — `LaneData.head` is the snap hash bit assigns a
|
|
335
|
+
* lane component, so comparing it (not looking at git) is the only way to name what the snap did.
|
|
336
|
+
*/
|
|
337
|
+
/**
|
|
338
|
+
* The lane entries the run SUMMARY diffs: the visible components plus the hidden `updateDependents`
|
|
339
|
+
* (a cascade export moves those heads too, and a summary reading "nothing changed" over one would be
|
|
340
|
+
* false). Summary-only — the fingerprint and the foreign-scope check keep reading `components` alone.
|
|
341
|
+
*/
|
|
342
|
+
export declare function laneSummaryComponents(lane: LaneData | undefined): LaneData['components'];
|
|
343
|
+
export declare function changedLaneComponents(before: LaneData['components'] | undefined, after: LaneData['components']): LaneData['components'];
|
|
344
|
+
/**
|
|
345
|
+
* What a sync run did to the scope — information the branch's own git diff cannot show, since the
|
|
346
|
+
* snap step can move a component's recorded version with no file in the PR ever changing. Kept to one
|
|
347
|
+
* maintained comment (`RUN_SUMMARY_MARKER` + `upsertComment`) rather than one post per push.
|
|
348
|
+
*/
|
|
349
|
+
export declare function runSummaryCommentBody({ laneIdStr, laneUrl, branch, branchTipSha, laneHead, changed, }: {
|
|
350
|
+
laneIdStr: string;
|
|
351
|
+
laneUrl: string;
|
|
352
|
+
branch: string;
|
|
353
|
+
branchTipSha: string | undefined;
|
|
354
|
+
laneHead: string;
|
|
355
|
+
changed: LaneData['components'];
|
|
356
|
+
}): string;
|
|
313
357
|
/**
|
|
314
358
|
* Resolution instructions posted on a halted PR. `note` replaces the default steps for halts where
|
|
315
359
|
* they do not apply (the branch-aliasing halt, whose PR belongs to a different lane).
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.REFUSED_SUMMARY_PREFIX = exports.LaneSyncExecutor = exports.KEPT_BECAUSE = exports.HALT_SUMMARY_PREFIX = void 0;
|
|
6
|
+
exports.RUN_SUMMARY_MARKER = exports.REFUSED_SUMMARY_PREFIX = exports.LaneSyncExecutor = exports.KEPT_BECAUSE = exports.HALT_SUMMARY_PREFIX = void 0;
|
|
7
7
|
exports.branchMirrorsOtherLaneNote = branchMirrorsOtherLaneNote;
|
|
8
8
|
exports.branchMirrorsOtherLaneReason = branchMirrorsOtherLaneReason;
|
|
9
9
|
exports.capEntries = capEntries;
|
|
10
|
+
exports.changedLaneComponents = changedLaneComponents;
|
|
10
11
|
exports.crossScopeDescription = crossScopeDescription;
|
|
11
12
|
exports.crossScopeMidFlightHaltReason = crossScopeMidFlightHaltReason;
|
|
12
13
|
exports.crossScopeRefusal = crossScopeRefusal;
|
|
@@ -16,9 +17,11 @@ exports.foreignLaneComponents = foreignLaneComponents;
|
|
|
16
17
|
exports.haltCommentBody = haltCommentBody;
|
|
17
18
|
exports.isProtectedBranch = isProtectedBranch;
|
|
18
19
|
exports.laneHeadFingerprint = laneHeadFingerprint;
|
|
20
|
+
exports.laneSummaryComponents = laneSummaryComponents;
|
|
19
21
|
exports.laneSyncPrBody = laneSyncPrBody;
|
|
20
22
|
exports.racedAdoptionPushSummary = racedAdoptionPushSummary;
|
|
21
23
|
exports.racedLedgerPushSummary = racedLedgerPushSummary;
|
|
24
|
+
exports.runSummaryCommentBody = runSummaryCommentBody;
|
|
22
25
|
function _chalk() {
|
|
23
26
|
const data = _interopRequireDefault(require("chalk"));
|
|
24
27
|
_chalk = function () {
|
|
@@ -147,6 +150,12 @@ function racedLedgerPushSummary(laneName, policyClause = '') {
|
|
|
147
150
|
function racedAdoptionPushSummary(laneName) {
|
|
148
151
|
return racedSummary(laneName, 'lane untouched; adoption ledger commit lost the push race — next run re-plans');
|
|
149
152
|
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* An HTML comment embedded in the run-summary comment's body, so `upsertComment` finds and replaces
|
|
156
|
+
* THIS comment on a later run rather than posting a duplicate summary on every push.
|
|
157
|
+
*/
|
|
158
|
+
const RUN_SUMMARY_MARKER = exports.RUN_SUMMARY_MARKER = '<!-- bit-ci-sync:run-summary -->';
|
|
150
159
|
/**
|
|
151
160
|
* Fingerprint of a lane's content — same primitive as `branchStateFingerprint`, so "equal" means
|
|
152
161
|
* exactly "the branch records every lane component at the lane's head".
|
|
@@ -376,12 +385,9 @@ class LaneSyncExecutor {
|
|
|
376
385
|
stateCommit: undefined,
|
|
377
386
|
bitmap: undefined,
|
|
378
387
|
hasDevCommits: false,
|
|
388
|
+
stateCommitBundlesSources: false,
|
|
379
389
|
tipMessage: ''
|
|
380
390
|
};
|
|
381
|
-
if ((0, _syncState().hasSyncMarker)(branchState.tipMessage)) {
|
|
382
|
-
logger.console('branch tip is a bit-sync commit; reconciler will no-op unless the lane moved');
|
|
383
|
-
}
|
|
384
|
-
|
|
385
391
|
// Read before any refusal below: every halt must be suppressible by the conflict label, or a
|
|
386
392
|
// standing problem re-comments on the same PR on every scheduled run.
|
|
387
393
|
const pr = await this.findPr(branch);
|
|
@@ -450,32 +456,25 @@ class LaneSyncExecutor {
|
|
|
450
456
|
// merely quotes the marker must not read as one we wrote.
|
|
451
457
|
const tipIsSyncCommit = (0, _syncState().isSyncAuthoredMessage)(branchState.tipMessage);
|
|
452
458
|
const hasIndependentHistory = await this.computeHasIndependentHistory(ownership, branchState.stateCommit, defaultBranch);
|
|
453
|
-
const branchNamesDifferentLane = await this.computeBranchNamesDifferentLane(lastSyncedHead, branchState.hasDevCommits, mirroredLane, laneIdStr, branch, defaultBranch);
|
|
459
|
+
const branchNamesDifferentLane = await this.computeBranchNamesDifferentLane(lastSyncedHead, branchState.hasDevCommits || branchState.stateCommitBundlesSources, mirroredLane, laneIdStr, branch, defaultBranch);
|
|
454
460
|
const action = (0, _syncPlanner().planLaneSync)({
|
|
455
461
|
laneHead,
|
|
456
462
|
branchExists,
|
|
457
463
|
lastSyncedHead,
|
|
458
464
|
hasDevCommits: branchState.hasDevCommits,
|
|
465
|
+
stateCommitBundlesSources: branchState.stateCommitBundlesSources,
|
|
459
466
|
tipIsSyncCommit,
|
|
460
467
|
hasIndependentHistory,
|
|
461
468
|
conflictLabelPresent,
|
|
462
469
|
branchNamesDifferentLane,
|
|
463
470
|
ownership
|
|
464
471
|
});
|
|
465
|
-
logger.console(_chalk().default.blue(`${laneName} -> ${action.type} (branch: ${branch}, lane head: ${laneHead?.slice(0, 9) ?? 'none'}, ` + `branch state: ${lastSyncedHead?.slice(0, 9) ?? 'none'}, ` + `dev commits: ${branchState.hasDevCommits}
|
|
472
|
+
logger.console(_chalk().default.blue(`${laneName} -> ${action.type} (branch: ${branch}, lane head: ${laneHead?.slice(0, 9) ?? 'none'}, ` + `branch state: ${lastSyncedHead?.slice(0, 9) ?? 'none'}, ` + `dev commits: ${branchState.hasDevCommits}` + `${branchState.stateCommitBundlesSources ? ' (+sources bundled into the state commit)' : ''}` + `${laneIsGone ? `, branch claim: ${ownership}` : ''})`));
|
|
466
473
|
if (dryRun) {
|
|
467
474
|
const line = dryRunSummaryLine(laneName, action);
|
|
468
475
|
logger.console((0, _cli().formatWarningSummary)(`Dry-run: ${line}`));
|
|
469
476
|
return line;
|
|
470
477
|
}
|
|
471
|
-
|
|
472
|
-
// A pair can be converged at the bit level while the tip still holds unsnapped source edits (a
|
|
473
|
-
// single commit that rewrites `.bitmap` AND carries an edit is its own state commit) — say so.
|
|
474
|
-
// `laneHead` must be checked explicitly: with no lane and no attribution both fingerprints are
|
|
475
|
-
// undefined, and `undefined === undefined` would fire on every ordinary developer branch.
|
|
476
|
-
if (action.type === 'noop' && action.reason === 'converged' && !tipIsSyncCommit && laneHead && lastSyncedHead === laneHead) {
|
|
477
|
-
logger.console((0, _cli().formatWarningSummary)(`converged on bit state, but ${branch}'s tip is not a bit ci sync commit — any source edits it ` + `carries that were never snapped stay invisible until the next commit on the branch`));
|
|
478
|
-
}
|
|
479
478
|
switch (action.type) {
|
|
480
479
|
case 'noop':
|
|
481
480
|
return `${laneName} -> noop (${action.reason})`;
|
|
@@ -492,29 +491,20 @@ class LaneSyncExecutor {
|
|
|
492
491
|
pr
|
|
493
492
|
});
|
|
494
493
|
case 'export-branch':
|
|
495
|
-
// The tip is already this reconciler's own commit — it already confirmed everything up to
|
|
496
|
-
// it (that is what writing it means), and `hasDevCommits`/`stateCommit` cannot tell a real
|
|
497
|
-
// dev commit from one that touches no bit-tracked file (docs, CI config): `stateCommit` is
|
|
498
|
-
// derived from `.bitmap`'s content, never from commit messages (sync-state.ts), so a commit
|
|
499
|
-
// that leaves `.bitmap` byte-identical never advances it, however many runs re-confirm
|
|
500
|
-
// "nothing to snap" on top. Recognizing our own tip here — not by loosening `hasDevCommits`
|
|
501
|
-
// itself, which the ownership/retirement path also reads and must stay strict — is what
|
|
502
|
-
// makes that settle instead of re-planning `export-branch` forever.
|
|
503
|
-
if (tipIsSyncCommit) {
|
|
504
|
-
return `${laneName} -> noop (converged; branch tip is already this reconciler's own sync commit)`;
|
|
505
|
-
}
|
|
506
494
|
return this.executeExportBranch({
|
|
507
495
|
target,
|
|
508
496
|
laneIdStr,
|
|
509
497
|
branch,
|
|
510
|
-
defaultBranch
|
|
498
|
+
defaultBranch,
|
|
499
|
+
preExportLane: remoteLane
|
|
511
500
|
});
|
|
512
501
|
case 'merge-diverged':
|
|
513
502
|
return this.executeMergeDiverged({
|
|
514
503
|
target,
|
|
515
504
|
laneIdStr,
|
|
516
505
|
branch,
|
|
517
|
-
defaultBranch
|
|
506
|
+
defaultBranch,
|
|
507
|
+
preExportLane: remoteLane
|
|
518
508
|
});
|
|
519
509
|
case 'adopt-branch':
|
|
520
510
|
return this.executeAdoptBranch({
|
|
@@ -671,23 +661,56 @@ class LaneSyncExecutor {
|
|
|
671
661
|
}
|
|
672
662
|
|
|
673
663
|
/**
|
|
674
|
-
* Push the branch's dev commits back onto the lane
|
|
675
|
-
*
|
|
664
|
+
* Push the branch's dev commits back onto the lane. Probe first, mutate second: like adoption, land
|
|
665
|
+
* the lane in the workspace keeping the BRANCH's files, then ask `bit status` whether the branch's
|
|
666
|
+
* tree holds anything the lane is missing. Nothing → converged, and nothing is written or pushed —
|
|
667
|
+
* which is why a suspected-work plan can repeat run after run without a memo, and why no commit
|
|
668
|
+
* message is ever consulted to suppress it. Something → snap + export, then commit the resulting
|
|
669
|
+
* `.bitmap` back onto the branch so the next run reads the two sides as converged.
|
|
676
670
|
*/
|
|
677
671
|
async executeExportBranch({
|
|
678
672
|
target,
|
|
679
673
|
laneIdStr,
|
|
680
674
|
branch,
|
|
681
|
-
defaultBranch
|
|
675
|
+
defaultBranch,
|
|
676
|
+
preExportLane
|
|
682
677
|
}) {
|
|
683
678
|
const {
|
|
684
679
|
logger
|
|
685
680
|
} = this.deps;
|
|
686
681
|
const laneName = target.name;
|
|
687
682
|
logger.console(_chalk().default.blue(`Exporting branch ${branch} onto lane ${laneIdStr}`));
|
|
688
|
-
const message = await this.lastNonSyncCommitMessage(branch, defaultBranch);
|
|
689
683
|
await this.checkoutFromRemote(branch, `origin/${branch}`);
|
|
690
684
|
try {
|
|
685
|
+
const switchErr = await this.materializeLane(laneIdStr, false);
|
|
686
|
+
if (switchErr) {
|
|
687
|
+
return await this.executeHalt({
|
|
688
|
+
laneName,
|
|
689
|
+
laneIdStr,
|
|
690
|
+
branch,
|
|
691
|
+
reason: `failed to check branch ${branch} against lane ${laneIdStr} before exporting: ${switchErr.message}`,
|
|
692
|
+
pr: await this.findPr(branch)
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
// The probe's "clean" answer means converged only against the lane the PLAN read. A lane that
|
|
696
|
+
// moved in between must re-plan (merge-diverged owns that shape): comparing the branch's files
|
|
697
|
+
// with the newer lane would misread the lane's own advance as branch work, and the export would
|
|
698
|
+
// put the older content on top of a concurrent developer's push. The materialize above just
|
|
699
|
+
// fetched, so this read and the status below see the same lane.
|
|
700
|
+
if (preExportLane) {
|
|
701
|
+
const laneNow = await this.getRemoteLane(target);
|
|
702
|
+
const movedTo = laneNow ? laneHeadFingerprint(laneNow.components) : undefined;
|
|
703
|
+
if (movedTo !== laneHeadFingerprint(preExportLane.components)) {
|
|
704
|
+
return `${laneName} -> noop (lane ${laneIdStr} moved while this run was planning; the next run re-plans)`;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
if (!(await this.deps.ci.hasUnsyncedWorkChanges())) {
|
|
708
|
+
logger.console(_chalk().default.green(`Branch ${branch}'s tree holds nothing lane ${laneIdStr} is missing`));
|
|
709
|
+
return `${laneName} -> noop (converged)`;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// Read only once the probe found work: it walks up to 200 commits, wasted on a converged probe.
|
|
713
|
+
const message = await this.lastNonSyncCommitMessage(branch, defaultBranch);
|
|
691
714
|
const exported = await this.snapAndExportOntoLane(laneIdStr, message);
|
|
692
715
|
if (exported.status === 'error') {
|
|
693
716
|
// Halt rather than propagate: one lane's failed snap/export must not abort the lanes after it.
|
|
@@ -699,6 +722,12 @@ class LaneSyncExecutor {
|
|
|
699
722
|
pr: await this.findPr(branch)
|
|
700
723
|
});
|
|
701
724
|
}
|
|
725
|
+
if (exported.status === 'noop') {
|
|
726
|
+
// `bit status` saw a difference the snap did not record. Nothing reached the lane and nothing
|
|
727
|
+
// is pushed, so the next run re-reads the identical state — visible-but-repeating beats a
|
|
728
|
+
// ledger commit asserting a convergence the status read just denied.
|
|
729
|
+
return `${laneName} -> noop (bit status reported unsynced changes but the snap found nothing to record)`;
|
|
730
|
+
}
|
|
702
731
|
const recorded = await this.recordLaneHeadOnBranch(target, laneIdStr, branch);
|
|
703
732
|
if (recorded.status === 'raced') return racedLedgerPushSummary(laneName);
|
|
704
733
|
if (recorded.status === 'unreadable') {
|
|
@@ -710,6 +739,17 @@ class LaneSyncExecutor {
|
|
|
710
739
|
pr: await this.findPr(branch)
|
|
711
740
|
});
|
|
712
741
|
}
|
|
742
|
+
|
|
743
|
+
// Best-effort: a comment failure must never undo a git/lane pair that already converged.
|
|
744
|
+
await this.postRunSummaryComment({
|
|
745
|
+
target,
|
|
746
|
+
laneIdStr,
|
|
747
|
+
branch,
|
|
748
|
+
branchTipSha: recorded.branchTipSha,
|
|
749
|
+
laneHead: recorded.laneHead,
|
|
750
|
+
preComponents: laneSummaryComponents(preExportLane),
|
|
751
|
+
postComponents: laneSummaryComponents(recorded.remoteLane)
|
|
752
|
+
});
|
|
713
753
|
return `${laneName} -> export-branch (lane ${laneIdStr} @ ${recorded.laneHead.slice(0, 9)}, ` + `branch ${branch} updated)`;
|
|
714
754
|
} finally {
|
|
715
755
|
await this.restoreWorkspace(defaultBranch);
|
|
@@ -793,7 +833,8 @@ class LaneSyncExecutor {
|
|
|
793
833
|
target,
|
|
794
834
|
laneIdStr,
|
|
795
835
|
branch,
|
|
796
|
-
defaultBranch
|
|
836
|
+
defaultBranch,
|
|
837
|
+
preExportLane
|
|
797
838
|
}) {
|
|
798
839
|
const {
|
|
799
840
|
logger
|
|
@@ -812,6 +853,17 @@ class LaneSyncExecutor {
|
|
|
812
853
|
// lane pointer and the merge base, and a stale local branch must never leak into the result.
|
|
813
854
|
await this.checkoutFromRemote(branch, `origin/${branch}`);
|
|
814
855
|
|
|
856
|
+
// Same freshness rule as the export probe: the plan's inputs must still describe the world.
|
|
857
|
+
// A lane that moved since planning re-plans — the next run merges the newer head — rather
|
|
858
|
+
// than proceeding on a divergence assessment the planner never made.
|
|
859
|
+
if (preExportLane) {
|
|
860
|
+
const laneNow = await this.getRemoteLane(target);
|
|
861
|
+
const movedTo = laneNow ? laneHeadFingerprint(laneNow.components) : undefined;
|
|
862
|
+
if (movedTo !== laneHeadFingerprint(preExportLane.components)) {
|
|
863
|
+
return `${laneName} -> noop (lane ${laneIdStr} moved while this run was planning; the next run re-plans)`;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
815
867
|
// ---- step 1: merge the lane's snaps into the branch's working tree -----------------------
|
|
816
868
|
const merge = await this.mergeLaneIntoBranchWorkingTree(laneIdStr);
|
|
817
869
|
if (merge.error) {
|
|
@@ -860,7 +912,21 @@ class LaneSyncExecutor {
|
|
|
860
912
|
if (recorded.status === 'unreadable') {
|
|
861
913
|
return await halt(`lane ${laneIdStr} could not be read back from the remote after the merge export`);
|
|
862
914
|
}
|
|
863
|
-
|
|
915
|
+
// The summary comment belongs only to runs that exported — same as the export path, and a
|
|
916
|
+
// nothing-new merge (the branch had nothing of its own) must not claim it exported.
|
|
917
|
+
if (exported.status === 'exported') {
|
|
918
|
+
await this.postRunSummaryComment({
|
|
919
|
+
target,
|
|
920
|
+
laneIdStr,
|
|
921
|
+
branch,
|
|
922
|
+
branchTipSha: recorded.branchTipSha,
|
|
923
|
+
laneHead: recorded.laneHead,
|
|
924
|
+
preComponents: laneSummaryComponents(preExportLane),
|
|
925
|
+
postComponents: laneSummaryComponents(recorded.remoteLane)
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
const exportClause = exported.status === 'exported' ? 'then exported' : 'the merge left nothing new to export';
|
|
929
|
+
return `${laneName} -> merge-diverged (${policyClause}merged lane into branch, ${exportClause}; lane ${laneIdStr} @ ` + `${recorded.laneHead.slice(0, 9)}, branch ${branch} updated)`;
|
|
864
930
|
} catch (e) {
|
|
865
931
|
// Something unforeseen — halt anyway: no single lane may abort the rest of the run.
|
|
866
932
|
return await halt(`unexpected failure while reconciling diverged lane ${laneIdStr} with branch ${branch}: ${e?.message || e}`);
|
|
@@ -908,7 +974,8 @@ class LaneSyncExecutor {
|
|
|
908
974
|
* so any earlier fingerprint is stale), commit — crucially the `.bitmap` the snap rewrote — and push.
|
|
909
975
|
* `unreadable` means the lane could no longer be read back, in which case the caller halts;
|
|
910
976
|
* `raced` means a concurrent run's push already got there first, in which case the caller reports
|
|
911
|
-
* a benign noop instead. `adopted` adds the audit-only `ADOPTION_TRAILER`.
|
|
977
|
+
* a benign noop instead. `adopted` adds the audit-only `ADOPTION_TRAILER`. The re-queried lane is
|
|
978
|
+
* also the caller's only cheap source of POST-export component versions.
|
|
912
979
|
*/
|
|
913
980
|
async recordLaneHeadOnBranch(target, laneIdStr, branch, opts = {}) {
|
|
914
981
|
const remoteLane = await this.getRemoteLane(target);
|
|
@@ -922,7 +989,9 @@ class LaneSyncExecutor {
|
|
|
922
989
|
};
|
|
923
990
|
return {
|
|
924
991
|
status: 'ok',
|
|
925
|
-
laneHead
|
|
992
|
+
laneHead,
|
|
993
|
+
remoteLane,
|
|
994
|
+
branchTipSha: pushResult.pushedSha
|
|
926
995
|
};
|
|
927
996
|
}
|
|
928
997
|
|
|
@@ -1052,8 +1121,8 @@ class LaneSyncExecutor {
|
|
|
1052
1121
|
}
|
|
1053
1122
|
|
|
1054
1123
|
/** Gates `differentLaneStillClaims` to the planner's first-contact path, the only one that reads it. */
|
|
1055
|
-
async computeBranchNamesDifferentLane(lastSyncedHead,
|
|
1056
|
-
if (lastSyncedHead || !
|
|
1124
|
+
async computeBranchNamesDifferentLane(lastSyncedHead, mayCarryWork, mirroredLane, laneIdStr, branch, defaultBranch) {
|
|
1125
|
+
if (lastSyncedHead || !mayCarryWork) return false;
|
|
1057
1126
|
return this.differentLaneStillClaims(mirroredLane, laneIdStr, branch, defaultBranch);
|
|
1058
1127
|
}
|
|
1059
1128
|
|
|
@@ -1324,6 +1393,50 @@ class LaneSyncExecutor {
|
|
|
1324
1393
|
}
|
|
1325
1394
|
}
|
|
1326
1395
|
|
|
1396
|
+
/**
|
|
1397
|
+
* Post (or update) ONE maintained comment on the branch's PR: what this export did to the scope —
|
|
1398
|
+
* the components it snapped and the synced branch/lane anchors — information the branch's git diff
|
|
1399
|
+
* cannot show, since the snap step alone can move dependency ranges the PR's files never touched.
|
|
1400
|
+
* Best-effort throughout: no configured git host, no open PR yet, or a provider that doesn't
|
|
1401
|
+
* implement `upsertComment` are all normal states, not failures — the git/lane pair already
|
|
1402
|
+
* converged without this comment. A comment API error warns and never fails the run.
|
|
1403
|
+
*/
|
|
1404
|
+
async postRunSummaryComment({
|
|
1405
|
+
target,
|
|
1406
|
+
laneIdStr,
|
|
1407
|
+
branch,
|
|
1408
|
+
branchTipSha,
|
|
1409
|
+
laneHead,
|
|
1410
|
+
preComponents,
|
|
1411
|
+
postComponents
|
|
1412
|
+
}) {
|
|
1413
|
+
const {
|
|
1414
|
+
gitHost,
|
|
1415
|
+
logger
|
|
1416
|
+
} = this.deps;
|
|
1417
|
+
if (!gitHost?.upsertComment) return;
|
|
1418
|
+
const pr = await this.findPr(branch);
|
|
1419
|
+
if (!pr) return;
|
|
1420
|
+
// Same URL shape as the lane-sync PR body (line ~1249): the scope that HOSTS the lane, not
|
|
1421
|
+
// necessarily `defaultScope`.
|
|
1422
|
+
const laneUrl = `https://${(0, _legacy().getCloudDomain)()}/${target.hostScope.replace('.', '/')}/~lane/${target.name}`;
|
|
1423
|
+
// Body construction sits INSIDE the guard: `currentBranchTip` is a git refetch and can reject,
|
|
1424
|
+
// and an export that already succeeded must never be failed by its own progress report.
|
|
1425
|
+
try {
|
|
1426
|
+
const body = runSummaryCommentBody({
|
|
1427
|
+
laneIdStr,
|
|
1428
|
+
laneUrl,
|
|
1429
|
+
branch,
|
|
1430
|
+
branchTipSha,
|
|
1431
|
+
laneHead,
|
|
1432
|
+
changed: changedLaneComponents(preComponents, postComponents)
|
|
1433
|
+
});
|
|
1434
|
+
await gitHost.upsertComment(pr.number, RUN_SUMMARY_MARKER, body);
|
|
1435
|
+
} catch (e) {
|
|
1436
|
+
logger.consoleWarning(`Could not post the run-summary comment on ${branch}'s PR: ${e?.message || e}`);
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1327
1440
|
/**
|
|
1328
1441
|
* The subject of the newest commit on the branch that isn't one of our own sync commits — it's
|
|
1329
1442
|
* the developer's own description of the change, and becomes the snap message on the lane.
|
|
@@ -1353,6 +1466,9 @@ class LaneSyncExecutor {
|
|
|
1353
1466
|
await (0, _gitOps().commitWithIdentity)(message, {
|
|
1354
1467
|
extraArgs: ['--allow-empty']
|
|
1355
1468
|
});
|
|
1469
|
+
// The sha of the commit this run is about to push — the summary anchors on it, never on a
|
|
1470
|
+
// refetch of the mutable remote tip (a concurrent dev push must not read as the synced state).
|
|
1471
|
+
const pushedSha = (await _git().git.revparse(['HEAD'])).trim() || undefined;
|
|
1356
1472
|
try {
|
|
1357
1473
|
// `HEAD:refs/heads/<branch>`: a full-ref destination cannot be resolved as a tag or
|
|
1358
1474
|
// reinterpreted — the configured branch name is user input (see `sync-config.ts`).
|
|
@@ -1376,7 +1492,8 @@ class LaneSyncExecutor {
|
|
|
1376
1492
|
}
|
|
1377
1493
|
this.deps.logger.console(_chalk().default.green(`Pushed ${branch}`));
|
|
1378
1494
|
return {
|
|
1379
|
-
raced: false
|
|
1495
|
+
raced: false,
|
|
1496
|
+
pushedSha
|
|
1380
1497
|
};
|
|
1381
1498
|
}
|
|
1382
1499
|
|
|
@@ -1492,6 +1609,41 @@ function laneSyncPrBody({
|
|
|
1492
1609
|
return [`Mirrors the Bit lane [\`${laneIdStr}\`](${laneUrl}) onto \`${branch}\`.`, '', `- lane: ${laneUrl}`, `- lane head: \`${laneHead}\``, '', `Components on the lane (${components.length}):`, listed || '_none_', '', 'This PR is maintained by `bit ci sync` — push to the branch to send changes back to the lane.'].join('\n');
|
|
1493
1610
|
}
|
|
1494
1611
|
|
|
1612
|
+
/**
|
|
1613
|
+
* The lane components this export actually changed: new on the lane, or moved to a new head. Diffed
|
|
1614
|
+
* by id against the lane's content BEFORE the export — `LaneData.head` is the snap hash bit assigns a
|
|
1615
|
+
* lane component, so comparing it (not looking at git) is the only way to name what the snap did.
|
|
1616
|
+
*/
|
|
1617
|
+
/**
|
|
1618
|
+
* The lane entries the run SUMMARY diffs: the visible components plus the hidden `updateDependents`
|
|
1619
|
+
* (a cascade export moves those heads too, and a summary reading "nothing changed" over one would be
|
|
1620
|
+
* false). Summary-only — the fingerprint and the foreign-scope check keep reading `components` alone.
|
|
1621
|
+
*/
|
|
1622
|
+
function laneSummaryComponents(lane) {
|
|
1623
|
+
return lane ? [...lane.components, ...(lane.updateDependents ?? [])] : [];
|
|
1624
|
+
}
|
|
1625
|
+
function changedLaneComponents(before, after) {
|
|
1626
|
+
const beforeHeads = new Map((before ?? []).map(comp => [comp.id.toStringWithoutVersion(), comp.head]));
|
|
1627
|
+
return after.filter(comp => beforeHeads.get(comp.id.toStringWithoutVersion()) !== comp.head);
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
/**
|
|
1631
|
+
* What a sync run did to the scope — information the branch's own git diff cannot show, since the
|
|
1632
|
+
* snap step can move a component's recorded version with no file in the PR ever changing. Kept to one
|
|
1633
|
+
* maintained comment (`RUN_SUMMARY_MARKER` + `upsertComment`) rather than one post per push.
|
|
1634
|
+
*/
|
|
1635
|
+
function runSummaryCommentBody({
|
|
1636
|
+
laneIdStr,
|
|
1637
|
+
laneUrl,
|
|
1638
|
+
branch,
|
|
1639
|
+
branchTipSha,
|
|
1640
|
+
laneHead,
|
|
1641
|
+
changed
|
|
1642
|
+
}) {
|
|
1643
|
+
const listed = capEntries(changed.map(comp => `- \`${comp.id.toStringWithoutVersion()}\` @ \`${comp.head.slice(0, 9)}\``), '- ').join('\n');
|
|
1644
|
+
return [RUN_SUMMARY_MARKER, `**bit ci sync** synced this branch with lane [\`${laneIdStr}\`](${laneUrl}).`, '', `Components snapped (${changed.length}):`, listed || '_none — nothing on the lane changed this run_', '', `- branch: \`${branch}\`${branchTipSha ? ` @ \`${branchTipSha.slice(0, 9)}\`` : ''}`, `- lane: [\`${laneIdStr}\`](${laneUrl}) @ \`${laneHead.slice(0, 9)}\``].join('\n');
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1495
1647
|
/**
|
|
1496
1648
|
* Single-quote a value interpolated into a copy-pasteable shell command. Lane names may contain `$` and
|
|
1497
1649
|
* `!`, and a configured branch name may contain anything git accepts as a ref — including a quote.
|