From 8413de98ca676ee3acc06aa27dfbce9ffb2b1e28 Mon Sep 17 00:00:00 2001 From: Samuel El-Borai Date: Thu, 21 Sep 2023 22:13:14 +0200 Subject: [PATCH] Support autoMerge commit filter without scope --- src/manifest.ts | 19 +- test/manifest.ts | 580 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 586 insertions(+), 13 deletions(-) diff --git a/src/manifest.ts b/src/manifest.ts index 84145fcdd..24a39c546 100644 --- a/src/manifest.ts +++ b/src/manifest.ts @@ -293,7 +293,7 @@ export type AutoMergeOption = { /** * Only auto merge if all conventional commits of the PR match the filter */ - conventionalCommitFilter?: {type: string; scope: string}[]; + conventionalCommitFilter?: {type: string; scope?: string}[]; /** * Only auto merge if the version bump match the filter */ @@ -1586,18 +1586,16 @@ export class Manifest { if (pullRequest.conventionalCommits.length === 0) { return false; } - const commitSet = new Set( - pullRequest.conventionalCommits.map( - commit => `${commit.type}:${commit.scope}` - ) - ); const filterSet = new Set( conventionalCommitFilter!.map( - filter => `${filter.type}:${filter.scope}` + filter => `${filter.type}:${filter.scope ? filter.scope : '*'}` ) ); - for (const n of commitSet) { - if (!filterSet.has(n)) { + for (const commit of pullRequest.conventionalCommits) { + if ( + !filterSet.has(`${commit.type}:${commit.scope}`) && + !filterSet.has(`${commit.type}:*`) + ) { return false; } } @@ -1605,7 +1603,7 @@ export class Manifest { }; const selected = - conventionalCommitFilter?.length && conventionalCommitFilter.length + conventionalCommitFilter?.length && versionBumpFilter?.length ? applyConventionalCommitFilter() && applyVersionBumpFilter() : conventionalCommitFilter?.length ? applyConventionalCommitFilter() @@ -1613,6 +1611,7 @@ export class Manifest { ? applyVersionBumpFilter() : // no filter provided false; + return selected ? this.autoMerge : undefined; } } diff --git a/test/manifest.ts b/test/manifest.ts index 1914269aa..426028ee3 100644 --- a/test/manifest.ts +++ b/test/manifest.ts @@ -4745,7 +4745,7 @@ version = "3.0.0" ]); }); - it('enables auto-merge when provided filters match the release pull request', async () => { + it('enables auto-merge when filters are provided (filters: version bump, commit type, commit scope)', async () => { const createPullRequestStub = sandbox .stub(github, 'createPullRequest') .resolves({ @@ -4787,11 +4787,21 @@ version = "3.0.0" releaseType: 'node', component: 'pkg3', }, + 'path/d': { + releaseType: 'node', + component: 'pkg4', + }, + 'path/e': { + releaseType: 'node', + component: 'pkg5', + }, }, { 'path/a': Version.parse('1.0.0'), 'path/b': Version.parse('1.0.0'), 'path/c': Version.parse('1.0.0'), + 'path/d': Version.parse('1.0.0'), + 'path/e': Version.parse('1.0.0'), }, { separatePullRequests: true, @@ -4872,6 +4882,288 @@ version = "3.0.0" }, ], }, + { + title: PullRequestTitle.ofTargetBranch('main', 'main'), + body: new PullRequestBody([]), + updates: [], + labels: [], + headRefName: 'release-please/branches/main/components/d', + draft: false, + version: Version.parse('1.1.0'), // minor bump, match filter + previousVersion: Version.parse('1.0.0'), + conventionalCommits: [ + { + type: 'fix', // type does match filter + scope: null, // no scope, does not match filter + notes: [], + references: [], + sha: 'commit123', + message: 'fix: something', + bareMessage: 'something', + breaking: false, + }, + ], + }, + { + title: PullRequestTitle.ofTargetBranch('main', 'main'), + body: new PullRequestBody([]), + updates: [], + labels: [], + headRefName: 'release-please/branches/main/components/e', + draft: false, + version: Version.parse('1.1.0'), // minor bump, match filter + previousVersion: Version.parse('1.0.0'), + conventionalCommits: [ + { + type: 'fix', // type does match filter + scope: 'other', // other scope, does not match filter + notes: [], + references: [], + sha: 'commit123', + message: 'fix(other): something', + bareMessage: 'something', + breaking: false, + }, + ], + }, + ]); + const getLabelsStub = sandbox + .stub(github, 'getLabels') + .resolves(['label-a']); + const createLabelsStub = sandbox.stub(github, 'createLabels').resolves(); + + const pullRequestNumbers = await manifest.createPullRequests(); + + expect(pullRequestNumbers).lengthOf(5); + sinon.assert.calledOnce(getLabelsStub); + sinon.assert.calledOnce(createLabelsStub); + + expect(createPullRequestStub.callCount).to.equal(5); + sinon.assert.calledWith( + createPullRequestStub, + sinon.match.has( + 'headBranchName', + 'release-please/branches/main/components/a' + ), + 'main', + 'main', + sinon.match.string, + sinon.match.array, + sinon.match({ + autoMerge: undefined, + }) + ); + sinon.assert.calledWith( + createPullRequestStub, + sinon.match.has( + 'headBranchName', + 'release-please/branches/main/components/b' + ), + 'main', + 'main', + sinon.match.string, + sinon.match.array, + sinon.match({ + autoMerge: {mergeMethod: 'rebase'}, + }) + ); + sinon.assert.calledWith( + createPullRequestStub, + sinon.match.has( + 'headBranchName', + 'release-please/branches/main/components/c' + ), + 'main', + 'main', + sinon.match.string, + sinon.match.array, + sinon.match({ + autoMerge: undefined, + }) + ); + sinon.assert.calledWith( + createPullRequestStub, + sinon.match.has( + 'headBranchName', + 'release-please/branches/main/components/d' + ), + 'main', + 'main', + sinon.match.string, + sinon.match.array, + sinon.match({ + autoMerge: undefined, + }) + ); + sinon.assert.calledWith( + createPullRequestStub, + sinon.match.has( + 'headBranchName', + 'release-please/branches/main/components/e' + ), + 'main', + 'main', + sinon.match.string, + sinon.match.array, + sinon.match({ + autoMerge: undefined, + }) + ); + }); + + it('enables auto-merge when filters are provided (filters: only commit type)', async () => { + const createPullRequestStub = sandbox + .stub(github, 'createPullRequest') + .resolves({ + number: 22, + title: 'pr title1', + body: 'pr body1', + headBranchName: 'release-please/branches/main', + baseBranchName: 'main', + labels: [], + files: [], + }); + sandbox + .stub(github, 'getFileContentsOnBranch') + .withArgs('README.md', 'main') + .resolves(buildGitHubFileRaw('some-content')); + mockPullRequests(github, []); + sandbox.stub(github, 'getPullRequest').withArgs(22).resolves({ + number: 22, + title: 'pr title1', + body: 'pr body1', + headBranchName: 'release-please/branches/main', + baseBranchName: 'main', + labels: [], + files: [], + }); + const manifest = new Manifest( + github, + 'main', + { + 'path/a': { + releaseType: 'node', + component: 'pkg1', + }, + 'path/b': { + releaseType: 'node', + component: 'pkg2', + }, + 'path/c': { + releaseType: 'node', + component: 'pkg3', + }, + 'path/d': { + releaseType: 'node', + component: 'pkg4', + }, + }, + { + 'path/a': Version.parse('1.0.0'), + 'path/b': Version.parse('1.0.0'), + 'path/c': Version.parse('1.0.0'), + 'path/d': Version.parse('1.0.0'), + }, + { + separatePullRequests: true, + autoMerge: { + mergeMethod: 'rebase', + conventionalCommitFilter: [{type: 'fix'}], // only filter on type + }, + } + ); + sandbox + .stub(manifest, 'buildPullRequests') + .withArgs(sinon.match.any, sinon.match.any) + .resolves([ + { + title: PullRequestTitle.ofTargetBranch('main', 'main'), + body: new PullRequestBody([]), + updates: [], + labels: [], + headRefName: 'release-please/branches/main/components/a', + draft: false, + version: Version.parse('1.1.0'), + previousVersion: Version.parse('1.0.0'), + conventionalCommits: [ + { + type: 'fix', // type match filter + scope: 'api', // some scope + notes: [], + references: [], + sha: 'commit123', + message: 'fix(api): something', + bareMessage: 'something', + breaking: false, + }, + ], + }, + { + title: PullRequestTitle.ofTargetBranch('main', 'main'), + body: new PullRequestBody([]), + updates: [], + labels: [], + headRefName: 'release-please/branches/main/components/b', + draft: false, + version: Version.parse('1.1.0'), + previousVersion: Version.parse('1.0.0'), + conventionalCommits: [ + { + type: 'fix', // type match filter + scope: 'other', // another scope + notes: [], + references: [], + sha: 'commit123', + message: 'fix(other): something', + bareMessage: 'something', + breaking: false, + }, + ], + }, + { + title: PullRequestTitle.ofTargetBranch('main', 'main'), + body: new PullRequestBody([]), + updates: [], + labels: [], + headRefName: 'release-please/branches/main/components/c', + draft: false, + version: Version.parse('1.1.0'), + previousVersion: Version.parse('1.0.0'), + conventionalCommits: [ + { + type: 'fix', // type does match filter + scope: null, // no scope + notes: [], + references: [], + sha: 'commit123', + message: 'feat(api): something', + bareMessage: 'something', + breaking: false, + }, + ], + }, + { + title: PullRequestTitle.ofTargetBranch('main', 'main'), + body: new PullRequestBody([]), + updates: [], + labels: [], + headRefName: 'release-please/branches/main/components/d', + draft: false, + version: Version.parse('1.1.0'), + previousVersion: Version.parse('1.0.0'), + conventionalCommits: [ + { + type: 'feat', // type does not match filter + scope: 'api', + notes: [], + references: [], + sha: 'commit123', + message: 'feat(api): something', + bareMessage: 'something', + breaking: false, + }, + ], + }, ]); const getLabelsStub = sandbox .stub(github, 'getLabels') @@ -4880,11 +5172,11 @@ version = "3.0.0" const pullRequestNumbers = await manifest.createPullRequests(); - expect(pullRequestNumbers).lengthOf(3); + expect(pullRequestNumbers).lengthOf(4); sinon.assert.calledOnce(getLabelsStub); sinon.assert.calledOnce(createLabelsStub); - sinon.assert.calledThrice(createPullRequestStub); + expect(createPullRequestStub.callCount).to.equal(4); sinon.assert.calledWith( createPullRequestStub, sinon.match.has( @@ -4895,10 +5187,264 @@ version = "3.0.0" 'main', sinon.match.string, sinon.match.array, + sinon.match({ + autoMerge: {mergeMethod: 'rebase'}, + }) + ); + sinon.assert.calledWith( + createPullRequestStub, + sinon.match.has( + 'headBranchName', + 'release-please/branches/main/components/b' + ), + 'main', + 'main', + sinon.match.string, + sinon.match.array, + sinon.match({ + autoMerge: {mergeMethod: 'rebase'}, + }) + ); + sinon.assert.calledWith( + createPullRequestStub, + sinon.match.has( + 'headBranchName', + 'release-please/branches/main/components/c' + ), + 'main', + 'main', + sinon.match.string, + sinon.match.array, + sinon.match({ + autoMerge: {mergeMethod: 'rebase'}, + }) + ); + sinon.assert.calledWith( + createPullRequestStub, + sinon.match.has( + 'headBranchName', + 'release-please/branches/main/components/d' + ), + 'main', + 'main', + sinon.match.string, + sinon.match.array, sinon.match({ autoMerge: undefined, }) ); + }); + + it('enables auto-merge when filters are provided (filters: multiple commit filters)', async () => { + const createPullRequestStub = sandbox + .stub(github, 'createPullRequest') + .resolves({ + number: 22, + title: 'pr title1', + body: 'pr body1', + headBranchName: 'release-please/branches/main', + baseBranchName: 'main', + labels: [], + files: [], + }); + sandbox + .stub(github, 'getFileContentsOnBranch') + .withArgs('README.md', 'main') + .resolves(buildGitHubFileRaw('some-content')); + mockPullRequests(github, []); + sandbox.stub(github, 'getPullRequest').withArgs(22).resolves({ + number: 22, + title: 'pr title1', + body: 'pr body1', + headBranchName: 'release-please/branches/main', + baseBranchName: 'main', + labels: [], + files: [], + }); + const manifest = new Manifest( + github, + 'main', + { + 'path/a': { + releaseType: 'node', + component: 'pkg1', + }, + 'path/b': { + releaseType: 'node', + component: 'pkg2', + }, + 'path/c': { + releaseType: 'node', + component: 'pkg3', + }, + 'path/d': { + releaseType: 'node', + component: 'pkg4', + }, + 'path/e': { + releaseType: 'node', + component: 'pkg5', + }, + }, + { + 'path/a': Version.parse('1.0.0'), + 'path/b': Version.parse('1.0.0'), + 'path/c': Version.parse('1.0.0'), + 'path/d': Version.parse('1.0.0'), + 'path/e': Version.parse('1.0.0'), + }, + { + separatePullRequests: true, + autoMerge: { + mergeMethod: 'rebase', + conventionalCommitFilter: [ + {type: 'fix'}, + {type: 'feat', scope: 'api'}, + ], + }, + } + ); + sandbox + .stub(manifest, 'buildPullRequests') + .withArgs(sinon.match.any, sinon.match.any) + .resolves([ + { + title: PullRequestTitle.ofTargetBranch('main', 'main'), + body: new PullRequestBody([]), + updates: [], + labels: [], + headRefName: 'release-please/branches/main/components/a', + draft: false, + version: Version.parse('1.1.0'), + previousVersion: Version.parse('1.0.0'), + conventionalCommits: [ + { + type: 'fix', // type match filter + scope: 'something', // some scope + notes: [], + references: [], + sha: 'commit123', + message: 'fix(something): something', + bareMessage: 'something', + breaking: false, + }, + ], + }, + { + title: PullRequestTitle.ofTargetBranch('main', 'main'), + body: new PullRequestBody([]), + updates: [], + labels: [], + headRefName: 'release-please/branches/main/components/b', + draft: false, + version: Version.parse('1.1.0'), + previousVersion: Version.parse('1.0.0'), + conventionalCommits: [ + { + type: 'feat', // type match filter + scope: 'api', // scope match filter + notes: [], + references: [], + sha: 'commit123', + message: 'feat(api): something', + bareMessage: 'something', + breaking: false, + }, + ], + }, + { + title: PullRequestTitle.ofTargetBranch('main', 'main'), + body: new PullRequestBody([]), + updates: [], + labels: [], + headRefName: 'release-please/branches/main/components/c', + draft: false, + version: Version.parse('1.1.0'), + previousVersion: Version.parse('1.0.0'), + conventionalCommits: [ + { + type: 'feat', // type does match filter + scope: null, // no scope, does not match filter + notes: [], + references: [], + sha: 'commit123', + message: 'feat: something', + bareMessage: 'something', + breaking: false, + }, + ], + }, + { + title: PullRequestTitle.ofTargetBranch('main', 'main'), + body: new PullRequestBody([]), + updates: [], + labels: [], + headRefName: 'release-please/branches/main/components/d', + draft: false, + version: Version.parse('1.1.0'), + previousVersion: Version.parse('1.0.0'), + conventionalCommits: [ + { + type: 'fix', // type does match filter + scope: null, // no scope, does match filter + notes: [], + references: [], + sha: 'commit123', + message: 'fix: something', + bareMessage: 'something', + breaking: false, + }, + ], + }, + { + title: PullRequestTitle.ofTargetBranch('main', 'main'), + body: new PullRequestBody([]), + updates: [], + labels: [], + headRefName: 'release-please/branches/main/components/e', + draft: false, + version: Version.parse('1.1.0'), + previousVersion: Version.parse('1.0.0'), + conventionalCommits: [ + { + type: 'fix', // type does match filter + scope: 'api', + notes: [], + references: [], + sha: 'commit123', + message: 'chore(something): something', + bareMessage: 'something', + breaking: false, + }, + ], + }, + ]); + const getLabelsStub = sandbox + .stub(github, 'getLabels') + .resolves(['label-a']); + const createLabelsStub = sandbox.stub(github, 'createLabels').resolves(); + + const pullRequestNumbers = await manifest.createPullRequests(); + + expect(pullRequestNumbers).lengthOf(5); + sinon.assert.calledOnce(getLabelsStub); + sinon.assert.calledOnce(createLabelsStub); + + expect(createPullRequestStub.callCount).to.equal(5); + sinon.assert.calledWith( + createPullRequestStub, + sinon.match.has( + 'headBranchName', + 'release-please/branches/main/components/a' + ), + 'main', + 'main', + sinon.match.string, + sinon.match.array, + sinon.match({ + autoMerge: {mergeMethod: 'rebase'}, + }) + ); sinon.assert.calledWith( createPullRequestStub, sinon.match.has( @@ -4927,6 +5473,34 @@ version = "3.0.0" autoMerge: undefined, }) ); + sinon.assert.calledWith( + createPullRequestStub, + sinon.match.has( + 'headBranchName', + 'release-please/branches/main/components/d' + ), + 'main', + 'main', + sinon.match.string, + sinon.match.array, + sinon.match({ + autoMerge: {mergeMethod: 'rebase'}, + }) + ); + sinon.assert.calledWith( + createPullRequestStub, + sinon.match.has( + 'headBranchName', + 'release-please/branches/main/components/e' + ), + 'main', + 'main', + sinon.match.string, + sinon.match.array, + sinon.match({ + autoMerge: {mergeMethod: 'rebase'}, + }) + ); }); it('updates an existing pull request', async () => {