public virtual void SaveChangeLog(ApplicationChangeLog changeLog, string outputPath) { var viewModel = new ApplicationChangeLogViewModel(m_Configuration, changeLog); var templateLoader = new FileSystemTemplateLoader(FileSystem); var templateContext = new TemplateContext() { TemplateLoader = templateLoader }; var rootScriptObject = new ScriptObject() { { "model", viewModel }, { "enumerable", new EnumerableFunctions() }, { "textelement", new TextElementFunctions() }, { "html_utilities", new HtmlUtilities() }, { "changelog", new ChangeLogFunctions() }, { "template_settings", TemplateSettings } }; templateContext.PushGlobal(rootScriptObject); try { var template = templateLoader.Load($"/main{TemplateFileExtension}"); var rendered = template.Render(templateContext); File.WriteAllText(outputPath, rendered); } catch (ScriptRuntimeException ex) { throw new TemplateExecutionException(ex.Message, ex); } }
public async Task <ChangeLogPipelineResult> RunAsync() { var changeLog = new ApplicationChangeLog(); var executedTasks = new List <ChangeLogTaskExecutionResult>(); var pendingTasks = GetTasksInExecutionOrder(); while (pendingTasks.Count > 0) { var task = pendingTasks.Dequeue(); var taskName = GetTaskName(task); m_Logger.LogDebug($"Running task '{taskName}'"); var result = await task.RunAsync(changeLog); m_Logger.LogDebug($"Task '{taskName}' completed with result '{result}'"); executedTasks.Add(new ChangeLogTaskExecutionResult(task, result)); if (result == ChangeLogTaskResult.Error) { m_Logger.LogDebug($"Task '{taskName}' failed, aborting execution of pipeline."); return(ChangeLogPipelineResult.CreateErrorResult(executedTasks, pendingTasks.ToList())); } } return(ChangeLogPipelineResult.CreateSuccessResult(executedTasks, Array.Empty <IChangeLogTask>(), changeLog)); }
public async Task <ChangeLogTaskResult> RunAsync(ApplicationChangeLog changeLog) { if (m_ProjectInfo == null) { return(ChangeLogTaskResult.Skipped); } m_Logger.LogInformation("Adding GitHub links to changelog"); var githubClient = m_GitHubClientFactory.CreateClient(m_ProjectInfo.Host); var rateLimit = await githubClient.Miscellaneous.GetRateLimits(); m_Logger.LogDebug($"GitHub rate limit: {rateLimit.Rate.Remaining} requests of {rateLimit.Rate.Limit} remaining"); foreach (var versionChangeLog in changeLog.ChangeLogs) { foreach (var entry in versionChangeLog.AllEntries) { await ProcessEntryAsync(githubClient, entry); } } return(ChangeLogTaskResult.Success); }
public async Task Task_fails_if_version_was_already_added_by_a_previous_task() { // ARRANGE var tags = new GitTag[] { new GitTag("1.2.3", new GitId("0123")), }; var repoMock = new Mock <IGitRepository>(MockBehavior.Strict); repoMock.Setup(x => x.GetTags()).Returns(tags); var sut = new LoadVersionsFromTagsTask(m_Logger, ChangeLogConfigurationLoader.GetDefaultConfiguration(), repoMock.Object); var changeLog = new ApplicationChangeLog() { new SingleVersionChangeLog(new VersionInfo(NuGetVersion.Parse("1.2.3"), new GitId("4567"))) }; // ACT var result = await sut.RunAsync(changeLog); // ASSERT Assert.Equal(ChangeLogTaskResult.Error, result); Assert.DoesNotContain(changeLog.Versions, x => x.Version == NuGetVersion.Parse("1.2.3") && x.Commit == new GitId("0123")); }
public async Task Run_does_not_add_any_versions_if_no_tag_patterns_are_specified() { // ARRANGE var tags = new GitTag[] { new GitTag("1.2.3-alpha", new GitId("01")), new GitTag("4.5.6", new GitId("02")) }; var repoMock = new Mock <IGitRepository>(MockBehavior.Strict); repoMock.Setup(x => x.GetTags()).Returns(tags); var config = new ChangeLogConfiguration() { TagPatterns = Array.Empty <string>() }; var sut = new LoadVersionsFromTagsTask(m_Logger, config, repoMock.Object); // ACT var changeLog = new ApplicationChangeLog(); var result = await sut.RunAsync(changeLog); // ASSERT Assert.NotNull(changeLog.Versions); Assert.Empty(changeLog.Versions); Assert.Equal(ChangeLogTaskResult.Skipped, result); }
protected override ChangeLogTaskResult Run(ApplicationChangeLog changeLog) { if (m_TagPatterns.Count == 0) { m_Logger.LogWarning("No tag patterns configured, skipping loading of versions from tags."); return(ChangeLogTaskResult.Skipped); } m_Logger.LogInformation("Loading versions from git tags"); foreach (var versionInfo in GetVersions()) { if (changeLog.ContainsVersion(versionInfo.Version)) { m_Logger.LogError($"Cannot add version '{versionInfo.Version}' from tags because the changelog already contains this version."); return(ChangeLogTaskResult.Error); } m_Logger.LogDebug($"Adding version '{versionInfo.Version}' to changelog"); var versionChangeLog = new SingleVersionChangeLog(versionInfo); changeLog.Add(versionChangeLog); } return(ChangeLogTaskResult.Success); }
public async Task Run_correctly_gets_version_from_tag_name_using_default_configuration(string tagName, string version) { // ARRANGE var tags = new GitTag[] { new GitTag(tagName, new GitId("0123")), }; var repoMock = new Mock <IGitRepository>(MockBehavior.Strict); repoMock.Setup(x => x.GetTags()).Returns(tags); var expectedVersion = SemanticVersion.Parse(version); var sut = new LoadVersionsFromTagsTask(m_Logger, ChangeLogConfigurationLoader.GetDefaultConfiguration(), repoMock.Object); // ACT var changeLog = new ApplicationChangeLog(); var result = await sut.RunAsync(changeLog); // ASSERT var versionInfo = Assert.Single(changeLog.Versions); Assert.Equal(expectedVersion, versionInfo.Version); Assert.Equal(ChangeLogTaskResult.Success, result); }
public void ChangeLog_is_converted_to_expected_Markdown_06() { // Changelog with only a single entry // (Changelog uses simpler format if there is only a single entry for a version) var versionChangeLog = GetSingleVersionChangeLog( "1.2.3", null, GetChangeLogEntry( scope: "api", type: "feat", summary: "Some change", body: new[] { "Changelog entry body Line1\r\nLine2", "Changelog entry body Line3\r\nLine4", }) ); var changeLog = new ApplicationChangeLog() { versionChangeLog }; Approve(changeLog); }
public void ChangeLog_is_converted_to_expected_Markdown_07() { // Changelog that includes breaking changes // Breaking changes must be included regardless of the change type var versionChangeLog = GetSingleVersionChangeLog( "1.2.3", null, GetChangeLogEntry( scope: "api", type: "feat", summary: "Some change", body: new[] { "Changelog entry body Line1\r\nLine2", "Changelog entry body Line3\r\nLine4", }), GetChangeLogEntry(scope: "cli", type: "fix", summary: "A bug was fixed"), GetChangeLogEntry(isBreakingChange: true, type: "refactor", summary: "Some breaking change"), GetChangeLogEntry(isBreakingChange: true, type: "fix", summary: "A breaking bugfix") ); var changeLog = new ApplicationChangeLog() { versionChangeLog }; Approve(changeLog); }
public async Task Run_removes_the_expected_changelog_entries(string versionRange) { // ARRANGE var config = new ChangeLogConfiguration() { VersionRange = versionRange }; var sut = new FilterVersionsTask(m_Logger, config); var version1ChangeLog = GetSingleVersionChangeLog("1.2.3", null); var version2ChangeLog = GetSingleVersionChangeLog("4.5.6", null); var changeLog = new ApplicationChangeLog() { version1ChangeLog, version2ChangeLog }; // ACT var result = await sut.RunAsync(changeLog); // ASSERT var remainingEntry = Assert.Single(changeLog.ChangeLogs); Assert.Equal(version2ChangeLog, remainingEntry); Assert.Equal(ChangeLogTaskResult.Success, result); }
public void ChangeLog_is_converted_to_expected_Markdown_05() { // Changelog that contains entry with a body var versionChangeLog = GetSingleVersionChangeLog( "1.2.3", null, GetChangeLogEntry( scope: "api", type: "feat", summary: "Some change", body: new[] { "Changelog entry body Line1\r\nLine2", "Changelog entry body Line3\r\nLine4", }), GetChangeLogEntry(scope: "cli", type: "fix", summary: "A bug was fixed") ); var changeLog = new ApplicationChangeLog() { versionChangeLog }; Approve(changeLog); }
public async Task Run_succeeds_if_the_current_version_already_exists_and_points_to_the_same_commit() { // ARRANGE var config = new ChangeLogConfiguration() { CurrentVersion = "1.2.3" }; var head = GetGitCommit(id: TestGitIds.Id1); m_RepositoryMock.Setup(x => x.Head).Returns(head); var changeLog = new ApplicationChangeLog() { GetSingleVersionChangeLog(version: "1.2.3", commitId: head.Id) }; var sut = new LoadCurrentVersionTask(m_Logger, config, m_RepositoryMock.Object); // ACT var result = await sut.RunAsync(changeLog); // ASSERT Assert.Equal(ChangeLogTaskResult.Success, result); var singleVersionChangeLog = Assert.Single(changeLog.ChangeLogs); Assert.Equal(NuGetVersion.Parse("1.2.3"), singleVersionChangeLog.Version.Version); }
public async Task Run_fails_if_the_current_version_already_exists_and_points_to_a_different_commit() { // ARRANGE var config = new ChangeLogConfiguration() { CurrentVersion = "1.2.3" }; var head = GetGitCommit(id: TestGitIds.Id1); m_RepositoryMock.Setup(x => x.Head).Returns(head); var changeLog = new ApplicationChangeLog() { GetSingleVersionChangeLog(version: "1.2.3", commitId: TestGitIds.Id2) }; var sut = new LoadCurrentVersionTask(m_Logger, config, m_RepositoryMock.Object); // ACT var result = await sut.RunAsync(changeLog); // ASSERT Assert.Equal(ChangeLogTaskResult.Error, result); }
public async Task Run_adds_expected_version_to_changelog() { // ARRANGE var config = new ChangeLogConfiguration() { CurrentVersion = "1.2.3" }; var head = GetGitCommit(id: TestGitIds.Id1); m_RepositoryMock.Setup(x => x.Head).Returns(head); var sut = new LoadCurrentVersionTask(m_Logger, config, m_RepositoryMock.Object); var changeLog = new ApplicationChangeLog(); // ACT var result = await sut.RunAsync(changeLog); // ASSERT Assert.Equal(ChangeLogTaskResult.Success, result); var addedVersion = Assert.Single(changeLog.Versions); Assert.Equal(NuGetVersion.Parse("1.2.3"), addedVersion.Version); Assert.Equal(head.Id, addedVersion.Commit); }
public async Task Run_ignores_unparsable_commit_messages() { // ARRANGE var repo = new Mock <IGitRepository>(MockBehavior.Strict); repo .Setup(x => x.GetCommits(null, It.IsAny <GitId>())) .Returns(new[] { GetGitCommit(commitMessage: "Not a conventional commit"), }); var sut = new ParseCommitsTask(m_Logger, m_DefaultConfiguration, repo.Object); var versionChangeLog = GetSingleVersionChangeLog("1.2.3", "01"); var changelog = new ApplicationChangeLog() { versionChangeLog }; // ACT var result = await sut.RunAsync(changelog); // ASSERT Assert.Equal(ChangeLogTaskResult.Success, result); Assert.NotNull(versionChangeLog.AllEntries); Assert.Empty(versionChangeLog.AllEntries); }
public void ChangeLog_is_converted_to_expected_Markdown_12() { // Changelog uses simpler format if there is only a single entry for a version // When a entry is a feature AND a breaking change, it must not count as two entries var versionChangeLog = GetSingleVersionChangeLog( "1.2.3", null, GetChangeLogEntry( scope: "api", type: "feat", summary: "Some change", body: new[] { "Changelog entry body Line1\r\nLine2", "Changelog entry body Line3\r\nLine4", }, breakingChangeDescriptions: new[] { "Description of breaking change", "Another breaking change" })); var changeLog = new ApplicationChangeLog() { versionChangeLog }; Approve(changeLog); }
public async Task Run_uses_configured_parser_setting_02() { // ARRANGE var config = ChangeLogConfigurationLoader.GetDefaultConfiguration(); config.Parser.Mode = ChangeLogConfiguration.ParserMode.Strict; var repo = new Mock <IGitRepository>(MockBehavior.Strict); repo .Setup(x => x.GetCommits(null, It.IsAny <GitId>())) .Returns(new[] { // commit message is only parsable in "Loose" mode GetGitCommit(commitMessage: "feat: Some Description\r\n" + "\r\n" + "\r\n" + "Message Body\r\n"), }); var sut = new ParseCommitsTask(m_Logger, config, repo.Object); var versionChangeLog = GetSingleVersionChangeLog("1.2.3", "01"); var changelog = new ApplicationChangeLog() { versionChangeLog }; // ACT var result = await sut.RunAsync(changelog); // ASSERT Assert.Equal(ChangeLogTaskResult.Success, result); Assert.NotNull(versionChangeLog.AllEntries); Assert.Empty(versionChangeLog.AllEntries); }
public void ChangeLog_is_converted_to_expected_Markdown_13() { // if a display name is configured for a scope, // it must be used in the output instead of the actual scope var config = ChangeLogConfigurationLoader.GetDefaultConfiguration(); config.Scopes = new[] { new ChangeLogConfiguration.ScopeConfiguration() { Name = "scope1", DisplayName = "Scope 1 Display Name" } }; var versionChangeLog = GetSingleVersionChangeLog( "1.2.3", null, GetChangeLogEntry(scope: "scope1", type: "feat", summary: "Some change"), GetChangeLogEntry(scope: "scope2", type: "fix", summary: "A bug was fixed") ); var changeLog = new ApplicationChangeLog() { versionChangeLog }; Approve(changeLog, config); }
/// <inheritdoc /> public async Task <ChangeLogTaskResult> RunAsync(ApplicationChangeLog changeLog) { var projectInfo = GetProjectInfo(); if (projectInfo != null) { m_Logger.LogDebug($"Enabling GitLab integration with settings: " + $"{nameof(projectInfo.Host)} = '{projectInfo.Host}', " + $"{nameof(projectInfo.Namespace)} = '{projectInfo.Namespace}', " + $"{nameof(projectInfo.Project)} = '{projectInfo.Project}'"); } else { m_Logger.LogWarning("Failed to determine GitLab project information. Disabling GitLab integration"); return(ChangeLogTaskResult.Skipped); } m_Logger.LogInformation("Adding GitLab links to change log"); var gitlabClient = m_ClientFactory.CreateClient(projectInfo.Host); foreach (var versionChangeLog in changeLog.ChangeLogs) { foreach (var entry in versionChangeLog.AllEntries) { await ProcessEntryAsync(gitlabClient, projectInfo, entry); } } return(ChangeLogTaskResult.Success); }
public void ChangeLog_is_converted_to_expected_Markdown_14() { // Footers must be included in the output var versionChangeLog = GetSingleVersionChangeLog( "1.2.3", null, GetChangeLogEntry(scope: "scope1", type: "feat", summary: "Some change", footers: new[] { new ChangeLogEntryFooter(new CommitMessageFooterName("See-Also"), "Issue #5") }), GetChangeLogEntry(scope: "scope2", type: "fix", summary: "A bug was fixed", footers: new[] { new ChangeLogEntryFooter(new CommitMessageFooterName("Reviewed-by"), "*****@*****.**") }) ); var changeLog = new ApplicationChangeLog() { versionChangeLog }; Approve(changeLog); }
public async Task Run_ignores_duplicate_versions_from_tags() { // ARRANGE var tags = new GitTag[] { new GitTag("v1.2.3", new GitId("0123")), new GitTag("4.5.6", new GitId("4567")), new GitTag("1.2.3", new GitId("8910")) }; var repoMock = new Mock <IGitRepository>(MockBehavior.Strict); repoMock.Setup(x => x.GetTags()).Returns(tags); var sut = new LoadVersionsFromTagsTask(m_Logger, ChangeLogConfigurationLoader.GetDefaultConfiguration(), repoMock.Object); // ACT var changeLog = new ApplicationChangeLog(); var result = await sut.RunAsync(changeLog); // ASSERT Assert.Contains(changeLog.Versions, x => x.Version == NuGetVersion.Parse("1.2.3") && x.Commit == new GitId("0123")); Assert.DoesNotContain(changeLog.Versions, x => x.Version == NuGetVersion.Parse("1.2.3") && x.Commit == new GitId("8910")); Assert.Equal(ChangeLogTaskResult.Success, result); }
public void ChangeLog_is_converted_to_expected_Markdown_18() { // all configured types are included in the output, in the configured order var config = ChangeLogConfigurationLoader.GetDefaultConfiguration(); config.EntryTypes = new[] { new ChangeLogConfiguration.EntryTypeConfiguration() { Type = "feat", DisplayName = "New Features" }, new ChangeLogConfiguration.EntryTypeConfiguration() { Type = "docs", DisplayName = "Documentation Updates" }, new ChangeLogConfiguration.EntryTypeConfiguration() { Type = "fix", DisplayName = "Fixed bugs" } }; var versionChangeLog = GetSingleVersionChangeLog("1.2.3", entries: new[] { GetChangeLogEntry(type: "docs", summary: "A documentation change"), GetChangeLogEntry(type: "fix", summary: "Some bug fix"), GetChangeLogEntry(type: "feat", summary: "Some feature"), }); var changeLog = new ApplicationChangeLog() { versionChangeLog }; Approve(changeLog, config); }
public async Task Run_adds_versions_from_tags() { // ARRANGE var tags = new GitTag[] { new GitTag("1.2.3-alpha", new GitId("01")), new GitTag("4.5.6", new GitId("02")) }; var repoMock = new Mock <IGitRepository>(MockBehavior.Strict); repoMock.Setup(x => x.GetTags()).Returns(tags); var sut = new LoadVersionsFromTagsTask(m_Logger, ChangeLogConfigurationLoader.GetDefaultConfiguration(), repoMock.Object); // ACT var changeLog = new ApplicationChangeLog(); var result = await sut.RunAsync(changeLog); // ASSERT Assert.All( tags, tag => { var version = NuGetVersion.Parse(tag.Name); Assert.Contains(new VersionInfo(version, tag.Commit), changeLog.Versions); }); Assert.Equal(ChangeLogTaskResult.Success, result); }
protected override ChangeLogTaskResult Run(ApplicationChangeLog changeLog) { if (String.IsNullOrEmpty(m_Configuration.VersionRange)) { return(ChangeLogTaskResult.Skipped); } if (!VersionRange.TryParse(m_Configuration.VersionRange, out var versionRange)) { m_Logger.LogError($"Failed to parse version range '{m_Configuration.VersionRange}'"); return(ChangeLogTaskResult.Error); } m_Logger.LogInformation($"Filtering changelog using version range '{versionRange}'"); foreach (var versionChangeLog in changeLog.ChangeLogs.ToArray()) { if (!versionRange.Satisfies(versionChangeLog.Version.Version, VersionComparison.Default)) { m_Logger.LogDebug($"Removing version '{versionChangeLog.Version.Version}' from changelog"); changeLog.Remove(versionChangeLog); } } return(ChangeLogTaskResult.Success); }
protected override ChangeLogTaskResult Run(ApplicationChangeLog changelog) { if (!changelog.Versions.Any()) { return(ChangeLogTaskResult.Skipped); } m_Logger.LogInformation("Checking for commit message overrides"); foreach (var versionChangeLog in changelog.ChangeLogs) { foreach (var commit in versionChangeLog.AllCommits.ToArray()) { if (TryGetOverrideMessage(commit, out var overrideMessage)) { var newCommit = commit.WithCommitMessage(overrideMessage); versionChangeLog.Remove(commit); versionChangeLog.Add(newCommit); } } } return(ChangeLogTaskResult.Success); }
/// <inheritdoc /> protected override ChangeLogTaskResult Run(ApplicationChangeLog changeLog) { if (String.IsNullOrEmpty(m_Configuration.CurrentVersion)) { return(ChangeLogTaskResult.Skipped); } if (!NuGetVersion.TryParse(m_Configuration.CurrentVersion, out var version)) { m_Logger.LogError($"Invalid 'currentVersion' setting: '{m_Configuration.CurrentVersion}' is not a valid version"); return(ChangeLogTaskResult.Error); } var head = m_Repository.Head; if (changeLog.ContainsVersion(version)) { var existingEntry = changeLog[version]; if (existingEntry.Version.Commit == head.Id) { m_Logger.LogError($"Skipping adding of current version '{version}' because the changelog already contains the version and references the same commit."); return(ChangeLogTaskResult.Success); } m_Logger.LogError($"Cannot add current version '{version}' because the changelog already contains this version."); return(ChangeLogTaskResult.Error); } m_Logger.LogDebug($"Adding version '{version.ToNormalizedString()}' (commit '{head.Id}', current repository HEAD)"); var versionInfo = new VersionInfo(version, head.Id); changeLog.Add(new SingleVersionChangeLog(versionInfo)); return(ChangeLogTaskResult.Success); }
public async Task Run_adds_all_commits_if_no_previous_version_exists() { // ARRANGE var repo = new Mock <IGitRepository>(MockBehavior.Strict); repo .Setup(x => x.GetCommits(null, It.IsAny <GitId>())) .Returns(new[] { GetGitCommit(TestGitIds.Id1, "Commit 1"), GetGitCommit(TestGitIds.Id2, "Commit 2") }); var sut = new LoadCommitsTask(m_Logger, repo.Object); var versionChangeLog = GetSingleVersionChangeLog("1.2.3", TestGitIds.Id1); var changelog = new ApplicationChangeLog() { versionChangeLog }; // ACT var result = await sut.RunAsync(changelog); // ASSERT Assert.Equal(ChangeLogTaskResult.Success, result); repo.Verify(x => x.GetCommits(It.IsAny <GitId?>(), It.IsAny <GitId>()), Times.Once); Assert.NotNull(versionChangeLog.AllCommits); Assert.Equal(2, versionChangeLog.AllCommits.Count); Assert.Contains(versionChangeLog.AllCommits, c => c.Id == TestGitIds.Id1); Assert.Contains(versionChangeLog.AllCommits, c => c.Id == TestGitIds.Id2); }
protected override ChangeLogTaskResult Run(ApplicationChangeLog changelog) { m_Logger.LogInformation("Converting commit references to entry references"); var entriesByCommitId = changelog.SelectMany(x => x.AllEntries).ToDictionary(x => x.Commit); foreach (var(currentEntry, footer) in EnumerateFootersWithEntries(changelog)) { if (footer.Value is CommitReferenceTextElement commitReference) { if (commitReference.CommitId == currentEntry.Commit) { m_Logger.LogDebug($"Ignoring self-reference in entry '{currentEntry.Commit}'"); continue; } if (entriesByCommitId.TryGetValue(commitReference.CommitId, out var referencedEntry)) { m_Logger.LogDebug($"Detected reference to entry '{referencedEntry.Commit}' from entry '{currentEntry.Commit}'"); footer.Value = new ChangeLogEntryReferenceTextElement(footer.Value.Text, referencedEntry); } else { m_Logger.LogDebug($"No entry for commit '{commitReference.CommitId}' found"); } } } return(ChangeLogTaskResult.Success); }
/// <summary> /// Generates a Markdown document from the specified change log /// </summary> protected virtual MdDocument GetChangeLogDocument(ApplicationChangeLog model) { return(new MdDocument( GetChangeLogHeaderBlock(model), GetChangeLogContentBlock(model) )); }
public async Task Execute_does_not_replace_commit_references_if_no_entry_can_be_found() { // ARRANGE var testData = new TestDataFactory(); var sut = new ResolveEntryReferencesTask(m_Logger); var footer = new ChangeLogEntryFooter( new CommitMessageFooterName("footer-name"), new CommitReferenceTextElement("some-text", TestGitIds.Id3) ); var changelog = new ApplicationChangeLog() { testData.GetSingleVersionChangeLog("1.0", entries: new[] { testData.GetChangeLogEntry(commit: TestGitIds.Id1), testData.GetChangeLogEntry(commit: TestGitIds.Id2, footers: new[] { footer }) }) }; // ACT var result = await sut.RunAsync(changelog); // ASSERT Assert.Equal(ChangeLogTaskResult.Success, result); var commitReference = Assert.IsType <CommitReferenceTextElement>(footer.Value); Assert.Equal("some-text", commitReference.Text); Assert.Equal(TestGitIds.Id3, commitReference.CommitId); }