diff --git a/packages/nx/changelog-renderer/index.spec.ts b/packages/nx/changelog-renderer/index.spec.ts index 7b3beb6df91f9..bfc4e6d4b636c 100644 --- a/packages/nx/changelog-renderer/index.spec.ts +++ b/packages/nx/changelog-renderer/index.spec.ts @@ -53,6 +53,7 @@ describe('defaultChangelogRenderer()', () => { }, ], isBreaking: false, + revertedHashes: [], affectedFiles: [ 'packages/pkg-a/src/index.ts', 'packages/pkg-b/src/index.ts', @@ -82,6 +83,7 @@ describe('defaultChangelogRenderer()', () => { }, ], isBreaking: false, + revertedHashes: [], affectedFiles: ['packages/pkg-b/src/index.ts'], }, { @@ -108,6 +110,7 @@ describe('defaultChangelogRenderer()', () => { }, ], isBreaking: false, + revertedHashes: [], affectedFiles: ['packages/pkg-a/src/index.ts'], }, { @@ -134,6 +137,7 @@ describe('defaultChangelogRenderer()', () => { }, ], isBreaking: false, + revertedHashes: [], affectedFiles: ['packages/pkg-b/src/index.ts'], }, { @@ -160,6 +164,7 @@ describe('defaultChangelogRenderer()', () => { }, ], isBreaking: false, + revertedHashes: [], affectedFiles: ['packages/pkg-a/src/index.ts'], }, ]; @@ -378,4 +383,160 @@ describe('defaultChangelogRenderer()', () => { ).toMatchInlineSnapshot(`""`); }); }); + + describe('revert commits', () => { + it('should generate a Revert section for the changelog if the reverted commit is not part of the same release', async () => { + const commitsWithOnlyRevert: GitCommit[] = [ + { + message: + 'Revert "fix(release): do not update dependents when they already use "*" (#20607)"', + shortHash: '6528e88aa', + author: { + name: 'James Henry', + email: 'jh@example.com', + }, + body: 'This reverts commit 6d68236d467812aba4557a2bc7f667157de80fdb.\n"\n\nM\tpackages/js/src/generators/release-version/release-version.spec.ts\nM\tpackages/js/src/generators/release-version/release-version.ts\n', + authors: [ + { + name: 'James Henry', + email: 'jh@example.com', + }, + ], + description: + 'Revert "fix(release): do not update dependents when they already use "*" (#20607)"', + type: 'revert', + scope: 'release', + references: [ + { + type: 'pull-request', + value: '#20607', + }, + { + value: '6528e88aa', + type: 'hash', + }, + ], + isBreaking: false, + revertedHashes: ['6d68236d467812aba4557a2bc7f667157de80fdb'], + affectedFiles: [ + 'packages/js/src/generators/release-version/release-version.spec.ts', + 'packages/js/src/generators/release-version/release-version.ts', + ], + }, + ]; + + const markdown = await defaultChangelogRenderer({ + projectGraph, + commits: commitsWithOnlyRevert, + releaseVersion: 'v1.1.0', + project: null, + entryWhenNoChanges: false, + changelogRenderOptions: { + includeAuthors: true, + }, + }); + + expect(markdown).toMatchInlineSnapshot(` + "## v1.1.0 + + + ### ⏪ Revert + + - **release:** Revert "fix(release): do not update dependents when they already use "*" (#20607)" + + ### ❤️ Thank You + + - James Henry" + `); + }); + + it('should strip both the original commit and its revert if they are both included in the current range of commits', async () => { + const commitsWithRevertAndOriginal: GitCommit[] = [ + { + message: + 'Revert "fix(release): do not update dependents when they already use "*" (#20607)"', + shortHash: '6528e88aa', + author: { + name: 'James Henry', + email: 'jh@example.com', + }, + body: 'This reverts commit 6d68236d467812aba4557a2bc7f667157de80fdb.\n"\n\nM\tpackages/js/src/generators/release-version/release-version.spec.ts\nM\tpackages/js/src/generators/release-version/release-version.ts\n', + authors: [ + { + name: 'James Henry', + email: 'jh@example.com', + }, + ], + description: + 'Revert "fix(release): do not update dependents when they already use "*" (#20607)"', + type: 'revert', + scope: 'release', + references: [ + { + type: 'pull-request', + value: '#20607', + }, + { + value: '6528e88aa', + type: 'hash', + }, + ], + isBreaking: false, + revertedHashes: ['6d68236d467812aba4557a2bc7f667157de80fdb'], + affectedFiles: [ + 'packages/js/src/generators/release-version/release-version.spec.ts', + 'packages/js/src/generators/release-version/release-version.ts', + ], + }, + { + message: + 'fix(release): do not update dependents when they already use "*" (#20607)', + shortHash: '6d68236d4', + author: { + name: 'James Henry', + email: 'jh@example.com', + }, + body: '"\n\nM\tpackages/js/src/generators/release-version/release-version.spec.ts\nM\tpackages/js/src/generators/release-version/release-version.ts\n', + authors: [ + { + name: 'James Henry', + email: 'jh@example.com', + }, + ], + description: 'do not update dependents when they already use "*"', + type: 'fix', + scope: 'release', + references: [ + { + type: 'pull-request', + value: '#20607', + }, + { + value: '6d68236d4', + type: 'hash', + }, + ], + isBreaking: false, + revertedHashes: [], + affectedFiles: [ + 'packages/js/src/generators/release-version/release-version.spec.ts', + 'packages/js/src/generators/release-version/release-version.ts', + ], + }, + ]; + + const markdown = await defaultChangelogRenderer({ + projectGraph, + commits: commitsWithRevertAndOriginal, + releaseVersion: 'v1.1.0', + project: null, + entryWhenNoChanges: false, + changelogRenderOptions: { + includeAuthors: true, + }, + }); + + expect(markdown).toMatchInlineSnapshot(`""`); + }); + }); }); diff --git a/packages/nx/changelog-renderer/index.ts b/packages/nx/changelog-renderer/index.ts index 85f9743344801..27fea8c6e52f4 100644 --- a/packages/nx/changelog-renderer/index.ts +++ b/packages/nx/changelog-renderer/index.ts @@ -79,8 +79,24 @@ const defaultChangelogRenderer: ChangelogRenderer = async ({ test: { title: '✅ Tests' }, style: { title: '🎨 Styles' }, ci: { title: '🤖 CI' }, + revert: { title: '⏪ Revert' }, }; + // If the current range of commits contains both a commit and its revert, we strip them both from the final list + for (const commit of commits) { + if (commit.type === 'revert') { + for (const revertedHash of commit.revertedHashes) { + const revertedCommit = commits.find((c) => + revertedHash.startsWith(c.shortHash) + ); + if (revertedCommit) { + commits.splice(commits.indexOf(revertedCommit), 1); + commits.splice(commits.indexOf(commit), 1); + } + } + } + } + // workspace root level changelog if (project === null) { // No changes for the workspace diff --git a/packages/nx/src/command-line/release/utils/git.ts b/packages/nx/src/command-line/release/utils/git.ts index fbcd854907b17..d16c99e4ce02d 100644 --- a/packages/nx/src/command-line/release/utils/git.ts +++ b/packages/nx/src/command-line/release/utils/git.ts @@ -30,6 +30,7 @@ export interface GitCommit extends RawGitCommit { authors: GitCommitAuthor[]; isBreaking: boolean; affectedFiles: string[]; + revertedHashes: string[]; } function escapeRegExp(string) { @@ -274,6 +275,7 @@ const CoAuthoredByRegex = /co-authored-by:\s*(?.+)(<(?.+)>)/gim; const PullRequestRE = /\([ a-z]*(#\d+)\s*\)/gm; const IssueRE = /(#\d+)/gm; const ChangedFileRegex = /(A|M|D|R\d*|C\d*)\t([^\t\n]*)\t?(.*)?/gm; +const RevertHashRE = /This reverts commit (?[\da-f]{40})./gm; export function parseGitCommit(commit: RawGitCommit): GitCommit | null { const match = commit.message.match(ConventionalCommitRegex); @@ -281,8 +283,6 @@ export function parseGitCommit(commit: RawGitCommit): GitCommit | null { return null; } - const type = match.groups.type; - const scope = match.groups.scope || ''; const isBreaking = Boolean(match.groups.breaking); @@ -303,6 +303,18 @@ export function parseGitCommit(commit: RawGitCommit): GitCommit | null { // Remove references and normalize description = description.replace(PullRequestRE, '').trim(); + let type = match.groups.type; + // Extract any reverted hashes, if applicable + const revertedHashes = []; + const matchedHashes = commit.body.matchAll(RevertHashRE); + for (const matchedHash of matchedHashes) { + revertedHashes.push(matchedHash.groups.hash); + } + if (revertedHashes.length) { + type = 'revert'; + description = commit.message; + } + // Find all authors const authors: GitCommitAuthor[] = [commit.author]; for (const match of commit.body.matchAll(CoAuthoredByRegex)) { @@ -333,6 +345,7 @@ export function parseGitCommit(commit: RawGitCommit): GitCommit | null { scope, references, isBreaking, + revertedHashes, affectedFiles, }; }