From 1883020b77829122ed368998752f0196d328c60d Mon Sep 17 00:00:00 2001 From: Gabriel Terwesten Date: Thu, 12 May 2022 13:47:19 +0200 Subject: [PATCH] feat: link to referenced issues/PRs in changelog (#292) --- packages/melos/lib/src/common/changelog.dart | 29 ++++-- .../melos/lib/src/common/git_repository.dart | 9 ++ packages/melos/test/changelog_test.dart | 89 ++++++++++++------- packages/melos/test/git_repository_test.dart | 20 +++++ 4 files changed, 111 insertions(+), 36 deletions(-) diff --git a/packages/melos/lib/src/common/changelog.dart b/packages/melos/lib/src/common/changelog.dart index d3771035..43f9faa7 100644 --- a/packages/melos/lib/src/common/changelog.dart +++ b/packages/melos/lib/src/common/changelog.dart @@ -23,6 +23,7 @@ import 'package:pub_semver/pub_semver.dart'; import '../package.dart'; import 'git_commit.dart'; +import 'git_repository.dart'; import 'pending_package_update.dart'; class Changelog { @@ -142,6 +143,13 @@ extension ChangelogStringBufferExtension on StringBuffer { } void writePackageUpdateChanges(MelosPendingPackageUpdate update) { + final config = update.workspace.config; + final repository = config.repository; + final linkToCommits = config.commands.version.linkToCommits ?? false; + + String processCommitHeader(String header) => + repository != null ? header.withIssueLinks(repository) : header; + // User provided changelog entry message. if (update.userChangelogMessage != null) { writeln(' - ${update.userChangelogMessage}'); @@ -162,17 +170,16 @@ extension ChangelogStringBufferExtension on StringBuffer { } if (parsedMessage.isMergeCommit) { - writePunctuated(parsedMessage.header); + writePunctuated(processCommitHeader(parsedMessage.header)); } else { writeBold(parsedMessage.type!.toUpperCase()); write(': '); - writePunctuated(parsedMessage.description!); + writePunctuated(processCommitHeader(parsedMessage.description!)); } - if (update.workspace.config.commands.version.linkToCommits ?? false) { + if (linkToCommits) { final shortCommitId = commit.id.substring(0, 8); - final commitUrl = - update.workspace.config.repository!.commitUrl(commit.id); + final commitUrl = repository!.commitUrl(commit.id); write(' ('); writeLink(shortCommitId, uri: commitUrl.toString()); write(')'); @@ -207,3 +214,15 @@ List _filteredAndSortedCommits( return commits; } + +// https://regex101.com/r/Q1IV9n/1 +final _issueLinkRegexp = RegExp(r'#(\d+)'); + +extension on String { + String withIssueLinks(HostedGitRepository repository) { + return replaceAllMapped(_issueLinkRegexp, (match) { + final issueUrl = repository.issueUrl(match.group(1)!); + return '[${match.group(0)}]($issueUrl)'; + }); + } +} diff --git a/packages/melos/lib/src/common/git_repository.dart b/packages/melos/lib/src/common/git_repository.dart index 9e9de2d6..5c2ff336 100644 --- a/packages/melos/lib/src/common/git_repository.dart +++ b/packages/melos/lib/src/common/git_repository.dart @@ -27,6 +27,9 @@ abstract class HostedGitRepository { /// The URL of the commit with the given [id] on the host's web site. Uri commitUrl(String id); + + /// The URL of the issue/PR with the given [id] on the host's web site. + Uri issueUrl(String id); } /// A git repository, hosted by GitHub. @@ -62,6 +65,9 @@ class GitHubRepository extends HostedGitRepository { @override Uri commitUrl(String id) => url.resolve('commit/$id'); + @override + Uri issueUrl(String id) => url.resolve('issues/$id'); + @override String toString() { return ''' @@ -116,6 +122,9 @@ class GitLabRepository extends HostedGitRepository { @override Uri commitUrl(String id) => url.resolve('-/commit/$id'); + @override + Uri issueUrl(String id) => url.resolve('-/issues/$id'); + @override String toString() { return ''' diff --git a/packages/melos/test/changelog_test.dart b/packages/melos/test/changelog_test.dart index ce4922da..4eb5e4f1 100644 --- a/packages/melos/test/changelog_test.dart +++ b/packages/melos/test/changelog_test.dart @@ -15,6 +15,7 @@ * */ +import 'package:melos/melos.dart'; import 'package:melos/src/common/changelog.dart'; import 'package:melos/src/common/git_commit.dart'; import 'package:melos/src/common/pending_package_update.dart'; @@ -25,42 +26,68 @@ import 'utils.dart'; void main() { group('linkToCommits', () { test('when enabled, adds link to commit behind each one', () { - final workspaceBuilder = VirtualWorkspaceBuilder( - ''' - repository: https://github.com/a/b - command: - version: - linkToCommits: true - ''', - )..addPackage( - ''' - name: a - ''', - ); - final workspace = workspaceBuilder.build(); - final package = workspace.allPackages['a']!; - final commit = RichGitCommit.tryParse( - GitCommit( - author: 'a', - id: 'b2841394a48cd7d84a4966a788842690e543b2ef', - date: DateTime.now(), - message: 'feat(a): b', - ), - )!; - final update = MelosPendingPackageUpdate( - workspace, - package, - [commit], - PackageUpdateReason.commit, - logger: workspace.logger, - ); - final changelog = MelosChangelog(update, workspace.logger); + final workspace = buildWorkspaceWithRepository(); + final package = workspace.allPackages['test_pkg']!; + final commit = testCommit(message: 'feat(a): b'); final commitUrl = workspace.config.repository!.commitUrl(commit.id); expect( - changelog.markdown, + renderCommitPackageUpdate(workspace, package, commit), contains('**FEAT**: b. ([${commit.id.substring(0, 8)}]($commitUrl))'), ); }); }); + + test('when repository is specified, adds links to referenced issues/PRs', () { + final workspace = buildWorkspaceWithRepository(linkToCommits: false); + final package = workspace.allPackages['test_pkg']!; + final commit = testCommit(message: 'feat(a): b (#123)'); + final issueUrl = workspace.config.repository!.issueUrl('123'); + + expect( + renderCommitPackageUpdate(workspace, package, commit), + contains('**FEAT**: b ([#123]($issueUrl)).'), + ); + }); +} + +MelosWorkspace buildWorkspaceWithRepository({bool linkToCommits = true}) { + final workspaceBuilder = VirtualWorkspaceBuilder( + ''' + repository: https://github.com/a/b + command: + version: + linkToCommits: $linkToCommits + ''', + )..addPackage( + ''' + name: test_pkg + ''', + ); + return workspaceBuilder.build(); +} + +RichGitCommit testCommit({required String message}) => RichGitCommit.tryParse( + GitCommit( + author: 'a', + id: 'b2841394a48cd7d84a4966a788842690e543b2ef', + date: DateTime.now(), + message: message, + ), + )!; + +String renderCommitPackageUpdate( + MelosWorkspace workspace, + Package package, + RichGitCommit commit, +) { + final update = MelosPendingPackageUpdate( + workspace, + package, + [commit], + PackageUpdateReason.commit, + logger: workspace.logger, + ); + final changelog = MelosChangelog(update, workspace.logger); + return changelog.markdown; } diff --git a/packages/melos/test/git_repository_test.dart b/packages/melos/test/git_repository_test.dart index fa34e6c2..a1c37ace 100644 --- a/packages/melos/test/git_repository_test.dart +++ b/packages/melos/test/git_repository_test.dart @@ -61,6 +61,16 @@ void main() { ), ); }); + + test('issueUrl returns correct URL', () { + final repo = GitHubRepository(owner: 'a', name: 'b'); + const issueId = '123'; + + expect( + repo.issueUrl(issueId), + Uri.parse('https://github.com/a/b/issues/123'), + ); + }); }); group('GitLabRepository', () { @@ -113,6 +123,16 @@ void main() { ), ); }); + + test('issueUrl returns correct URL', () { + final repo = GitLabRepository(owner: 'a', name: 'b'); + const issueId = '123'; + + expect( + repo.issueUrl(issueId), + Uri.parse('https://gitlab.com/a/b/-/issues/123'), + ); + }); }); group('parseHostedGitRepositoryUrl', () {