Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: link to referenced issues/PRs in changelog #292

Merged
merged 2 commits into from
May 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 24 additions & 5 deletions packages/melos/lib/src/common/changelog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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}');
Expand All @@ -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(')');
Expand Down Expand Up @@ -207,3 +214,15 @@ List<RichGitCommit> _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)';
});
}
}
9 changes: 9 additions & 0 deletions packages/melos/lib/src/common/git_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 '''
Expand Down Expand Up @@ -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 '''
Expand Down
89 changes: 58 additions & 31 deletions packages/melos/test/changelog_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
}
20 changes: 20 additions & 0 deletions packages/melos/test/git_repository_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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', () {
Expand Down Expand Up @@ -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', () {
Expand Down