├── src ├── App │ ├── AssemblyInfo.cs │ ├── FodyWeavers.xml │ ├── packages.config │ ├── ReleaseNotesCompiler.CLI.csproj │ └── Program.cs ├── Compiler │ ├── FodyWeavers.xml │ ├── AssemblyInfo.cs │ ├── IssueExtensions.cs │ ├── packages.config │ ├── IGitHubClient.cs │ ├── ReleaseUpdateRequired.cs │ ├── MilestoneExtensions.cs │ ├── DefaultGitHubClient.cs │ ├── OctokitExtensions.cs │ ├── ReleaseNotesCompiler.csproj │ └── ReleaseNotesBuilder.cs ├── .nuget │ └── packages.config ├── Tests │ ├── ApprovalTestConfig.cs │ ├── ReleaseNotesBuilderTests.NoCommitsNoIssues.approved.txt │ ├── ReleaseNotesBuilderTests.SomeCommitsNoIssues.approved.txt │ ├── ReleaseNotesBuilderTests.SingularCommitsNoIssues.approved.txt │ ├── ReleaseNotesBuilderTests.NoCommitsSingularIssues.approved.txt │ ├── packages.config │ ├── ReleaseNotesBuilderTests.SomeCommitsSingularIssues.approved.txt │ ├── ReleaseNotesBuilderTests.SingularCommitsSingularIssues.approved.txt │ ├── ClipBoardHelper.cs │ ├── ReleaseNotesBuilderTests.NoCommitsSomeIssues.approved.txt │ ├── ReleaseNotesBuilderTests.SingularCommitsSomeIssues.approved.txt │ ├── ReleaseNotesBuilderTests.SomeCommitsSomeIssues.approved.txt │ ├── ClientBuilder.cs │ ├── ReleaseNotesBuilderIntegrationTests.cs │ ├── FakeGitHubClient.cs │ ├── Helper.cs │ ├── ReleaseNotesBuilderTests.cs │ └── ReleaseNotesCompiler.Tests.csproj ├── nuget.config ├── GitHubReleaseNotes.sln └── GitHubReleaseNotes.sln.DotSettings ├── .gitignore ├── README.md ├── packaging └── nuget │ └── releasenotescompiler.cli.nuspec ├── LICENSE └── .gitattributes /src/App/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | [assembly: AssemblyCompany("Particular")] -------------------------------------------------------------------------------- /src/App/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/Compiler/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/.nuget/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/Tests/ApprovalTestConfig.cs: -------------------------------------------------------------------------------- 1 | using ApprovalTests.Reporters; 2 | 3 | [assembly: UseReporter(typeof(AllFailingTestsClipboardReporter), typeof(DiffReporter))] -------------------------------------------------------------------------------- /src/Compiler/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | [assembly: AssemblyTitle("ReleaseNotesCompiler")] 4 | [assembly: AssemblyProduct("ReleaseNotesCompiler")] -------------------------------------------------------------------------------- /src/Tests/ReleaseNotesBuilderTests.NoCommitsNoIssues.approved.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ## Where to get it 5 | You can download this release from [nuget](https://www.nuget.org/profiles/nservicebus/) -------------------------------------------------------------------------------- /src/Tests/ReleaseNotesBuilderTests.SomeCommitsNoIssues.approved.txt: -------------------------------------------------------------------------------- 1 | As part of this release we had [5 commits](https://github.com/TestUser/FakeRepo/commits/1.2.3). 2 | 3 | 4 | ## Where to get it 5 | You can download this release from [nuget](https://www.nuget.org/profiles/nservicebus/) -------------------------------------------------------------------------------- /src/Tests/ReleaseNotesBuilderTests.SingularCommitsNoIssues.approved.txt: -------------------------------------------------------------------------------- 1 | As part of this release we had [1 commit](https://github.com/TestUser/FakeRepo/commits/1.2.3). 2 | 3 | 4 | ## Where to get it 5 | You can download this release from [nuget](https://www.nuget.org/profiles/nservicebus/) -------------------------------------------------------------------------------- /src/Tests/ReleaseNotesBuilderTests.NoCommitsSingularIssues.approved.txt: -------------------------------------------------------------------------------- 1 | As part of this release we had [1 issue](https://github.com/FakeRepo/issues/issues?milestone=0&state=closed) closed. 2 | 3 | 4 | __Bugs__ 5 | - [__#1__](http://example.com/1) Issue 1 6 | 7 | ## Where to get it 8 | You can download this release from [nuget](https://www.nuget.org/profiles/nservicebus/) -------------------------------------------------------------------------------- /src/Compiler/IssueExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace ReleaseNotesCompiler 2 | { 3 | using System.Linq; 4 | using Octokit; 5 | 6 | static class IssueExtensions 7 | { 8 | public static bool IsBug(this Issue issue) 9 | { 10 | return issue.Labels.Any(label => label.Name == "Type: Bug" || label.Name == "Bug"); 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/Tests/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/Tests/ReleaseNotesBuilderTests.SomeCommitsSingularIssues.approved.txt: -------------------------------------------------------------------------------- 1 | As part of this release we had [5 commits](https://github.com/TestUser/FakeRepo/commits/1.2.3) which resulted in [1 issue](https://github.com/FakeRepo/issues/issues?milestone=0&state=closed) being closed. 2 | 3 | 4 | __Bugs__ 5 | - [__#1__](http://example.com/1) Issue 1 6 | 7 | ## Where to get it 8 | You can download this release from [nuget](https://www.nuget.org/profiles/nservicebus/) -------------------------------------------------------------------------------- /src/Tests/ReleaseNotesBuilderTests.SingularCommitsSingularIssues.approved.txt: -------------------------------------------------------------------------------- 1 | As part of this release we had [1 commit](https://github.com/TestUser/FakeRepo/commits/1.2.3) which resulted in [1 issue](https://github.com/FakeRepo/issues/issues?milestone=0&state=closed) being closed. 2 | 3 | 4 | __Bugs__ 5 | - [__#1__](http://example.com/1) Issue 1 6 | 7 | ## Where to get it 8 | You can download this release from [nuget](https://www.nuget.org/profiles/nservicebus/) -------------------------------------------------------------------------------- /src/Compiler/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/Compiler/IGitHubClient.cs: -------------------------------------------------------------------------------- 1 | namespace ReleaseNotesCompiler 2 | { 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | using Octokit; 6 | 7 | public interface IGitHubClient 8 | { 9 | Task GetNumberOfCommitsBetween(Milestone previousMilestone, Milestone currentMilestone); 10 | Task> GetIssues(Milestone targetMilestone); 11 | Task> GetMilestones(); 12 | } 13 | } -------------------------------------------------------------------------------- /src/Tests/ClipBoardHelper.cs: -------------------------------------------------------------------------------- 1 | namespace ReleaseNotesCompiler.Tests 2 | { 3 | using System.Threading; 4 | using System.Windows.Forms; 5 | 6 | class ClipBoardHelper 7 | { 8 | public static void SetClipboard(string result) 9 | { 10 | var thread = new Thread(() => Clipboard.SetText(result)); 11 | thread.SetApartmentState(ApartmentState.STA); 12 | thread.Start(); 13 | thread.Join(); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Tests/ReleaseNotesBuilderTests.NoCommitsSomeIssues.approved.txt: -------------------------------------------------------------------------------- 1 | As part of this release we had [3 issues](https://github.com/FakeRepo/issues/issues?milestone=0&state=closed) closed. 2 | 3 | 4 | __Bugs__ 5 | - [__#1__](http://example.com/1) Issue 1 6 | 7 | __Improvements/Features__ 8 | - [__#2__](http://example.com/2) Issue 2 9 | - [__#3__](http://example.com/3) Issue 3 10 | 11 | ## Where to get it 12 | You can download this release from [nuget](https://www.nuget.org/profiles/nservicebus/) -------------------------------------------------------------------------------- /src/Compiler/ReleaseUpdateRequired.cs: -------------------------------------------------------------------------------- 1 | namespace ReleaseNotesCompiler 2 | { 3 | public class ReleaseUpdateRequired 4 | { 5 | public string Repository { get; set; } 6 | public string Release { get; set; } 7 | public bool NeedsToBeCreated { get; set; } 8 | public override string ToString() 9 | { 10 | return string.Format("Update required for release {0}(Repo: {1}), NeedsToBeCreated: {2}", Release,Repository, NeedsToBeCreated); 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/Tests/ReleaseNotesBuilderTests.SingularCommitsSomeIssues.approved.txt: -------------------------------------------------------------------------------- 1 | As part of this release we had [1 commit](https://github.com/TestUser/FakeRepo/commits/1.2.3) which resulted in [3 issues](https://github.com/FakeRepo/issues/issues?milestone=0&state=closed) being closed. 2 | 3 | 4 | __Bugs__ 5 | - [__#1__](http://example.com/1) Issue 1 6 | 7 | __Improvements/Features__ 8 | - [__#2__](http://example.com/2) Issue 2 9 | - [__#3__](http://example.com/3) Issue 3 10 | 11 | ## Where to get it 12 | You can download this release from [nuget](https://www.nuget.org/profiles/nservicebus/) -------------------------------------------------------------------------------- /src/Tests/ReleaseNotesBuilderTests.SomeCommitsSomeIssues.approved.txt: -------------------------------------------------------------------------------- 1 | As part of this release we had [5 commits](https://github.com/TestUser/FakeRepo/commits/1.2.3) which resulted in [3 issues](https://github.com/FakeRepo/issues/issues?milestone=0&state=closed) being closed. 2 | 3 | 4 | __Bugs__ 5 | - [__#1__](http://example.com/1) Issue 1 6 | 7 | __Improvements/Features__ 8 | - [__#2__](http://example.com/2) Issue 2 9 | - [__#3__](http://example.com/3) Issue 3 10 | 11 | ## Where to get it 12 | You can download this release from [nuget](https://www.nuget.org/profiles/nservicebus/) -------------------------------------------------------------------------------- /src/Compiler/MilestoneExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReleaseNotesCompiler 4 | { 5 | using Octokit; 6 | 7 | static class MilestoneExtensions 8 | { 9 | internal static Version Version(this Milestone ver) 10 | { 11 | var nameWithoutPrerelease = ver.Title.Split('-')[0]; 12 | Version parsedVersion; 13 | 14 | if (!System.Version.TryParse(nameWithoutPrerelease, out parsedVersion)) 15 | { 16 | return new Version(0, 0); 17 | } 18 | 19 | return parsedVersion; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/App/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | nugets 2 | build32 3 | binaries 4 | obj 5 | bin 6 | *.vshost.* 7 | .nu 8 | _ReSharper.* 9 | _UpgradeReport.* 10 | *.csproj.user 11 | *.resharper.user 12 | *.resharper 13 | *.suo 14 | *.cache 15 | *~ 16 | *.swp 17 | *.user 18 | TestResults 19 | TestResult.xml 20 | results 21 | CommonAssemblyInfo.cs 22 | lib/sqlite/System.Data.SQLite.dll 23 | *.orig 24 | Samples/DataBus/storage 25 | packages 26 | PrecompiledWeb 27 | core-only 28 | Release 29 | Artifacts 30 | LogFiles 31 | csx 32 | *.ncrunchproject 33 | *.ncrunchsolution 34 | _NCrunch_NServiceBus/* 35 | logs 36 | run-git.cmd 37 | src/Chocolatey/Build/* 38 | App_Packages 39 | -------------------------------------------------------------------------------- /src/nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | GitHubReleaseNotes 2 | ==================== 3 | 4 | **This project is no longer maintained. Consider using https://github.com/GitTools/GitReleaseManager instead** 5 | 6 | Generates release notes for a milestone in a GitHub repo based on issues associated with the milestone. 7 | 8 | ### Conventions 9 | 10 | * All closed issues/PR's for a milestone will be included. 11 | * Issues/PR's with a label `Type: Bug` will be included in a `Bugs` section 12 | * Issues/PR's not labeled as `Type: Bug` will be included in a `Features/Improvements` section 13 | * Milestones are named `{major.minor.patch}`. 14 | * The version is picked up from the build number (GFV) and that info is used to find the milestone. 15 | * Release notes are generated as markdown. 16 | -------------------------------------------------------------------------------- /src/Tests/ClientBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace ReleaseNotesCompiler.Tests 2 | { 3 | using Octokit; 4 | using Octokit.Internal; 5 | 6 | public static class ClientBuilder 7 | { 8 | public static GitHubClient Build() 9 | { 10 | var credentialStore = new InMemoryCredentialStore(Helper.Credentials); 11 | var httpClient = new HttpClientAdapter(HttpMessageHandlerFactory.CreateDefault); 12 | 13 | var connection = new Connection( 14 | new ProductHeaderValue("ReleaseNotesCompiler"), 15 | GitHubClient.GitHubApiUrl, 16 | credentialStore, 17 | httpClient, 18 | new SimpleJsonSerializer()); 19 | 20 | return new GitHubClient(connection); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packaging/nuget/releasenotescompiler.cli.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | GitHubReleaseNotes 5 | Generates release notes 6 | $version$ 7 | NServiceBus Ltd 8 | Particular et al 9 | https://github.com/Particular/GitHubReleaseNotes 10 | Create release notes in markdown given a GH milestone 11 | github release nptes 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/Tests/ReleaseNotesBuilderIntegrationTests.cs: -------------------------------------------------------------------------------- 1 | namespace ReleaseNotesCompiler.Tests 2 | { 3 | using System; 4 | using System.Threading.Tasks; 5 | using NUnit.Framework; 6 | using ReleaseNotesCompiler; 7 | 8 | [TestFixture] 9 | public class ReleaseNotesBuilderIntegrationTests 10 | { 11 | [Explicit] 12 | [TestCase("NServiceBus", "5.1.0")] 13 | [TestCase("ServiceControl", "1.0.0-Beta4")] 14 | [TestCase("NServiceBus", "6.0.0")] 15 | public async Task GenerateReleaseNotes(string repo, string version) 16 | { 17 | var gitHubClient = ClientBuilder.Build(); 18 | 19 | var releaseNotesBuilder = new ReleaseNotesBuilder(new DefaultGitHubClient(gitHubClient, "Particular", repo), "Particular", repo, version); 20 | var result = await releaseNotesBuilder.BuildReleaseNotes(); 21 | Console.WriteLine(result); 22 | ClipBoardHelper.SetClipboard(result); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Particular Software 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/Tests/FakeGitHubClient.cs: -------------------------------------------------------------------------------- 1 | namespace ReleaseNotesCompiler.Tests 2 | { 3 | using System.Collections.Generic; 4 | using System.Collections.ObjectModel; 5 | using System.Threading.Tasks; 6 | using Octokit; 7 | using IGitHubClient = ReleaseNotesCompiler.IGitHubClient; 8 | 9 | public class FakeGitHubClient : IGitHubClient 10 | { 11 | public List Milestones { get; set; } 12 | public List Issues { get; set; } 13 | public int NumberOfCommits { get; set; } 14 | 15 | public FakeGitHubClient() 16 | { 17 | Milestones = new List(); 18 | Issues = new List(); 19 | } 20 | 21 | public Task GetNumberOfCommitsBetween(Milestone previousMilestone, Milestone currentMilestone) 22 | { 23 | return Task.FromResult(NumberOfCommits); 24 | } 25 | 26 | public Task> GetIssues(Milestone targetMilestone) 27 | { 28 | return Task.FromResult(Issues); 29 | } 30 | 31 | public Task> GetMilestones() 32 | { 33 | return Task.FromResult>(new ReadOnlyCollection(Milestones)); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform CRLF normalization 2 | * text=auto eol=crlf 3 | 4 | # Standard to msysgit 5 | *.doc diff=astextplain 6 | *.DOC diff=astextplain 7 | *.docx diff=astextplain 8 | *.DOCX diff=astextplain 9 | *.dot diff=astextplain 10 | *.DOT diff=astextplain 11 | *.pdf diff=astextplain 12 | *.PDF diff=astextplain 13 | *.rtf diff=astextplain 14 | *.RTF diff=astextplain 15 | 16 | *.exe -text -diff 17 | *.jpg -text -diff 18 | *.png -text -diff 19 | *.gif -text -diff 20 | *.dll -text -diff 21 | *.pdb -text -diff 22 | *.pptx -text -diff 23 | *.xap -text -diff 24 | *.ico -text -diff 25 | *.ttf -text -diff 26 | *.otf -text -diff 27 | 28 | *.cs text diff=csharp 29 | *.config text diff=csharp 30 | *.xml text diff=csharp 31 | *.vb text 32 | *.c text 33 | *.cpp text 34 | *.cxx text 35 | *.h text 36 | *.hxx text 37 | *.py text 38 | *.rb text 39 | *.java text 40 | *.html text 41 | *.htm text 42 | *.css text 43 | *.scss text 44 | *.sass text 45 | *.less text 46 | *.js text 47 | *.lisp text 48 | *.clj text 49 | *.sql text 50 | *.php text 51 | *.lua text 52 | *.m text 53 | *.asm text 54 | *.erl text 55 | *.fs text 56 | *.fsx text 57 | *.hs text 58 | 59 | *.psm1 text 60 | *.ps1 text 61 | *.txt text eol=crlf 62 | *.bat text eol=crlf 63 | 64 | # Custom for Visual Studio 65 | *.csproj merge=union 66 | *.vbproj merge=union 67 | *.fsproj merge=union 68 | *.dbproj merge=union 69 | *.sln text eol=crlf merge=union 70 | *.suo -text -diff 71 | *.snk -text -diff 72 | *.cub -text -diff 73 | *.wixlib -text -diff 74 | 75 | 76 | *.approved.* binary 77 | -------------------------------------------------------------------------------- /src/Tests/Helper.cs: -------------------------------------------------------------------------------- 1 | namespace ReleaseNotesCompiler.Tests 2 | { 3 | using System; 4 | using System.Net; 5 | using Octokit; 6 | 7 | public static class Helper 8 | { 9 | // From https://github.com/octokit/octokit.net/blob/master/Octokit.Tests.Integration/Helper.cs 10 | 11 | static readonly Lazy _credentialsThunk = new Lazy(() => 12 | { 13 | var githubUsername = Environment.GetEnvironmentVariable("OCTOKIT_GITHUBUSERNAME"); 14 | 15 | var githubToken = Environment.GetEnvironmentVariable("OCTOKIT_OAUTHTOKEN"); 16 | 17 | if (githubToken != null) 18 | return new Credentials(githubToken); 19 | 20 | var githubPassword = Environment.GetEnvironmentVariable("OCTOKIT_GITHUBPASSWORD"); 21 | 22 | if (githubUsername == null || githubPassword == null) 23 | return Credentials.Anonymous; 24 | 25 | return new Credentials(githubUsername, githubPassword); 26 | }); 27 | 28 | public static Credentials Credentials 29 | { 30 | get { return _credentialsThunk.Value; } 31 | } 32 | 33 | public static IWebProxy Proxy 34 | { 35 | get 36 | { 37 | return null; 38 | /* 39 | return new WebProxy( 40 | new System.Uri("http://myproxy:42"), 41 | true, 42 | new string[] {}, 43 | new NetworkCredential(@"domain\login", "password")); 44 | */ 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Compiler/DefaultGitHubClient.cs: -------------------------------------------------------------------------------- 1 | namespace ReleaseNotesCompiler 2 | { 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Octokit; 7 | 8 | public class DefaultGitHubClient : IGitHubClient 9 | { 10 | GitHubClient gitHubClient; 11 | string user; 12 | string repository; 13 | 14 | public DefaultGitHubClient(GitHubClient gitHubClient, string user, string repository) 15 | { 16 | this.gitHubClient = gitHubClient; 17 | this.user = user; 18 | this.repository = repository; 19 | } 20 | 21 | public async Task GetNumberOfCommitsBetween(Milestone previousMilestone, Milestone currentMilestone) 22 | { 23 | try 24 | { 25 | if (previousMilestone == null) 26 | { 27 | var gitHubClientRepositoryCommitsCompare = await gitHubClient.Repository.Commit.Compare(user, repository, "master", currentMilestone.Title); 28 | return gitHubClientRepositoryCommitsCompare.AheadBy; 29 | } 30 | 31 | var compareResult = await gitHubClient.Repository.Commit.Compare(user, repository, previousMilestone.Title, "master"); 32 | return compareResult.AheadBy; 33 | } 34 | catch (NotFoundException) 35 | { 36 | //If there is not tag yet the Compare will return a NotFoundException 37 | //we can safely ignore 38 | return 0; 39 | } 40 | } 41 | 42 | public async Task> GetIssues(Milestone targetMilestone) 43 | { 44 | var allIssues = await gitHubClient.AllIssuesForMilestone(targetMilestone); 45 | return allIssues.Where(x => x.State == ItemState.Closed).ToList(); 46 | } 47 | 48 | public Task> GetMilestones() 49 | { 50 | var milestonesClient = gitHubClient.Issue.Milestone; 51 | return milestonesClient.GetAllForRepository(user, repository, new MilestoneRequest 52 | { 53 | State = ItemStateFilter.All 54 | }); 55 | 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /src/Compiler/OctokitExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Octokit; 5 | 6 | namespace ReleaseNotesCompiler 7 | { 8 | public static class OctokitExtensions 9 | { 10 | public static bool IsPullRequest(this Issue issue) 11 | { 12 | return issue.PullRequest != null; 13 | } 14 | 15 | public static async Task> AllIssuesForMilestone(this GitHubClient gitHubClient, Milestone milestone) 16 | { 17 | var closedIssueRequest = new RepositoryIssueRequest 18 | { 19 | Milestone = milestone.Number.ToString(), 20 | State = ItemStateFilter.Closed 21 | }; 22 | var openIssueRequest = new RepositoryIssueRequest 23 | { 24 | Milestone = milestone.Number.ToString(), 25 | State = ItemStateFilter.Open 26 | }; 27 | var parts = milestone.Url.AbsolutePath.Split('/'); 28 | var user = parts[2]; 29 | var repository = parts[3]; 30 | var closedIssues = await gitHubClient.Issue.GetAllForRepository(user, repository, closedIssueRequest); 31 | var openIssues = await gitHubClient.Issue.GetAllForRepository(user, repository, openIssueRequest); 32 | return openIssues.Union(closedIssues); 33 | } 34 | 35 | public static string HtmlUrl(this Milestone milestone) 36 | { 37 | var parts = milestone.Url.AbsolutePath.Split('/'); 38 | var user = parts[2]; 39 | var repository = parts[3]; 40 | return string.Format("https://github.com/{0}/{1}/issues?milestone={2}&state=closed", user, repository, milestone.Number); 41 | } 42 | 43 | static IEnumerable FixHeaders(IEnumerable lines) 44 | { 45 | var inCode = false; 46 | foreach (var line in lines) 47 | { 48 | if (line.StartsWith("```")) 49 | { 50 | inCode = !inCode; 51 | } 52 | if (!inCode && line.StartsWith("#")) 53 | { 54 | yield return "###" + line; 55 | } 56 | else 57 | { 58 | yield return line; 59 | } 60 | } 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /src/GitHubReleaseNotes.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.24720.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReleaseNotesCompiler", "Compiler\ReleaseNotesCompiler.csproj", "{B02A026E-CA3A-48F4-BBA9-EB337B0A2035}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReleaseNotesCompiler.Tests", "Tests\ReleaseNotesCompiler.Tests.csproj", "{FAD045A3-CF63-48CA-BA49-8F4D79E3EF4F}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReleaseNotesCompiler.CLI", "App\ReleaseNotesCompiler.CLI.csproj", "{F1163F09-3D4E-4F95-AF46-24C15AB297FB}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{69196B89-C58C-4705-AF1C-AE6B1042D01D}" 13 | ProjectSection(SolutionItems) = preProject 14 | .nuget\packages.config = .nuget\packages.config 15 | EndProjectSection 16 | EndProject 17 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NuSpecs", "NuSpecs", "{87E2E61F-5F5D-45F1-8C4A-98E0BF8CA01D}" 18 | ProjectSection(SolutionItems) = preProject 19 | ..\packaging\nuget\releasenotescompiler.cli.nuspec = ..\packaging\nuget\releasenotescompiler.cli.nuspec 20 | EndProjectSection 21 | EndProject 22 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{CCFB4E86-2A64-4E73-A96E-67D58130C960}" 23 | ProjectSection(SolutionItems) = preProject 24 | ..\README.md = ..\README.md 25 | EndProjectSection 26 | EndProject 27 | Global 28 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 29 | Debug|Any CPU = Debug|Any CPU 30 | Release|Any CPU = Release|Any CPU 31 | EndGlobalSection 32 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 33 | {B02A026E-CA3A-48F4-BBA9-EB337B0A2035}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {B02A026E-CA3A-48F4-BBA9-EB337B0A2035}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {B02A026E-CA3A-48F4-BBA9-EB337B0A2035}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 | {B02A026E-CA3A-48F4-BBA9-EB337B0A2035}.Release|Any CPU.Build.0 = Release|Any CPU 37 | {FAD045A3-CF63-48CA-BA49-8F4D79E3EF4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {FAD045A3-CF63-48CA-BA49-8F4D79E3EF4F}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {FAD045A3-CF63-48CA-BA49-8F4D79E3EF4F}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {FAD045A3-CF63-48CA-BA49-8F4D79E3EF4F}.Release|Any CPU.Build.0 = Release|Any CPU 41 | {F1163F09-3D4E-4F95-AF46-24C15AB297FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 42 | {F1163F09-3D4E-4F95-AF46-24C15AB297FB}.Debug|Any CPU.Build.0 = Debug|Any CPU 43 | {F1163F09-3D4E-4F95-AF46-24C15AB297FB}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | {F1163F09-3D4E-4F95-AF46-24C15AB297FB}.Release|Any CPU.Build.0 = Release|Any CPU 45 | EndGlobalSection 46 | GlobalSection(SolutionProperties) = preSolution 47 | HideSolutionNode = FALSE 48 | EndGlobalSection 49 | EndGlobal 50 | -------------------------------------------------------------------------------- /src/Tests/ReleaseNotesBuilderTests.cs: -------------------------------------------------------------------------------- 1 | namespace ReleaseNotesCompiler.Tests 2 | { 3 | using System; 4 | using System.Linq; 5 | using ApprovalTests; 6 | using NUnit.Framework; 7 | using Octokit; 8 | 9 | [TestFixture] 10 | public class ReleaseNotesBuilderTests 11 | { 12 | [Test] 13 | public void NoCommitsNoIssues() 14 | { 15 | AcceptTest(0); 16 | } 17 | 18 | [Test] 19 | public void NoCommitsSomeIssues() 20 | { 21 | AcceptTest(0, CreateIssue(1, "Bug"), CreateIssue(2, "Feature"), CreateIssue(3, "Improvement")); 22 | } 23 | 24 | [Test] 25 | public void SomeCommitsNoIssues() 26 | { 27 | AcceptTest(5); 28 | } 29 | 30 | [Test] 31 | public void SomeCommitsSomeIssues() 32 | { 33 | AcceptTest(5, CreateIssue(1, "Bug"), CreateIssue(2, "Feature"), CreateIssue(3, "Improvement")); 34 | } 35 | 36 | [Test] 37 | public void SingularCommitsNoIssues() 38 | { 39 | AcceptTest(1); 40 | } 41 | 42 | [Test] 43 | public void SingularCommitsSomeIssues() 44 | { 45 | AcceptTest(1, CreateIssue(1, "Bug"), CreateIssue(2, "Feature"), CreateIssue(3, "Improvement")); 46 | } 47 | 48 | [Test] 49 | public void SingularCommitsSingularIssues() 50 | { 51 | AcceptTest(1, CreateIssue(1, "Bug")); 52 | } 53 | 54 | [Test] 55 | public void NoCommitsSingularIssues() 56 | { 57 | AcceptTest(0, CreateIssue(1, "Bug")); 58 | } 59 | 60 | [Test] 61 | public void SomeCommitsSingularIssues() 62 | { 63 | AcceptTest(5, CreateIssue(1, "Bug")); 64 | } 65 | 66 | static void AcceptTest(int commits, params Issue[] issues) 67 | { 68 | var fakeClient = new FakeGitHubClient(); 69 | 70 | fakeClient.Milestones.Add(CreateMilestone("1.2.3")); 71 | 72 | fakeClient.NumberOfCommits = commits; 73 | 74 | foreach (var issue in issues) 75 | { 76 | fakeClient.Issues.Add(issue); 77 | } 78 | 79 | var builder = new ReleaseNotesBuilder(fakeClient, "TestUser", "FakeRepo", "1.2.3"); 80 | 81 | var notes = builder.BuildReleaseNotes().Result; 82 | 83 | Approvals.Verify(notes); 84 | } 85 | 86 | 87 | static Milestone CreateMilestone(string version) 88 | { 89 | return new Milestone(new Uri("https://github.com/Particular/FakeRepo/issues?q=milestone%3A" + version), 0, ItemState.Open, version, String.Empty, null, 0, 0, DateTimeOffset.Now, null, null); 90 | } 91 | 92 | static Issue CreateIssue(int number, params string[] labels) 93 | { 94 | return new Issue(null, 95 | new Uri("http://example.com/" + number), 96 | null, 97 | null, 98 | number, 99 | ItemState.Open, 100 | "Issue " + number, 101 | "Some issue", 102 | null, 103 | null, 104 | labels.Select(x => new Label(null, x, null)).ToArray(), 105 | null, 106 | null, 107 | 0, 108 | null, 109 | null, 110 | DateTimeOffset.Now, 111 | null, 112 | 1, 113 | false, 114 | null); 115 | } 116 | } 117 | } -------------------------------------------------------------------------------- /src/Compiler/ReleaseNotesCompiler.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {B02A026E-CA3A-48F4-BBA9-EB337B0A2035} 8 | Library 9 | Properties 10 | ReleaseNotesCompiler 11 | ReleaseNotesCompiler 12 | v4.6 13 | 512 14 | 15 | 16 | ..\ 17 | true 18 | 19 | 20 | 21 | true 22 | full 23 | false 24 | bin\Debug\ 25 | DEBUG;TRACE 26 | prompt 27 | 4 28 | 29 | 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | ..\packages\Octokit.0.22.0\lib\net45\Octokit.dll 40 | True 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/Tests/ReleaseNotesCompiler.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {FAD045A3-CF63-48CA-BA49-8F4D79E3EF4F} 8 | Library 9 | Properties 10 | ReleaseNotesCompiler.Tests 11 | ReleaseNotesCompiler.Tests 12 | v4.6 13 | 512 14 | ..\ 15 | true 16 | 17 | 18 | 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | full 29 | false 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | true 35 | 36 | 37 | 38 | ..\packages\ApprovalTests.3.0.13\lib\net40\ApprovalTests.dll 39 | True 40 | 41 | 42 | ..\packages\ApprovalUtilities.3.0.13\lib\net45\ApprovalUtilities.dll 43 | True 44 | 45 | 46 | ..\packages\ApprovalUtilities.3.0.13\lib\net45\ApprovalUtilities.Net45.dll 47 | True 48 | 49 | 50 | ..\packages\NUnit.3.5.0\lib\net45\nunit.framework.dll 51 | True 52 | 53 | 54 | ..\packages\Octokit.0.22.0\lib\net45\Octokit.dll 55 | True 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | {B02A026E-CA3A-48F4-BBA9-EB337B0A2035} 79 | ReleaseNotesCompiler 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 89 | 90 | 91 | -------------------------------------------------------------------------------- /src/Compiler/ReleaseNotesBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Octokit; 8 | 9 | namespace ReleaseNotesCompiler 10 | { 11 | using static System.String; 12 | 13 | public class ReleaseNotesBuilder 14 | { 15 | public ReleaseNotesBuilder(IGitHubClient gitHubClient, string user, string repository, string milestoneTitle) 16 | { 17 | this.gitHubClient = gitHubClient; 18 | this.user = user; 19 | this.repository = repository; 20 | this.milestoneTitle = milestoneTitle; 21 | } 22 | 23 | public async Task BuildReleaseNotes() 24 | { 25 | var milestones = await gitHubClient.GetMilestones(); 26 | 27 | var targetMilestone = milestones.FirstOrDefault(x => x.Title == milestoneTitle); 28 | 29 | if (targetMilestone == null) 30 | { 31 | throw new Exception($"Could not find milestone for '{milestoneTitle}'."); 32 | } 33 | var issues = await gitHubClient.GetIssues(targetMilestone); 34 | var stringBuilder = new StringBuilder(); 35 | var previousMilestone = GetPreviousMilestone(targetMilestone, milestones); 36 | var numberOfCommits = await gitHubClient.GetNumberOfCommitsBetween(previousMilestone, targetMilestone); 37 | 38 | if (issues.Count > 0) 39 | { 40 | var issuesText = Format(issues.Count == 1 ? "{0} issue" : "{0} issues", issues.Count); 41 | 42 | if (numberOfCommits > 0) 43 | { 44 | var commitsLink = GetCommitsLink(targetMilestone, previousMilestone); 45 | var commitsText = Format(numberOfCommits == 1 ? "{0} commit" : "{0} commits", numberOfCommits); 46 | 47 | stringBuilder.Append($"As part of this release we had [{commitsText}]({commitsLink}) which resulted in [{issuesText}]({targetMilestone.HtmlUrl()}) being closed."); 48 | } 49 | else 50 | { 51 | stringBuilder.Append($"As part of this release we had [{issuesText}]({targetMilestone.HtmlUrl()}) closed."); 52 | } 53 | } 54 | else if (numberOfCommits > 0) 55 | { 56 | var commitsLink = GetCommitsLink(targetMilestone, previousMilestone); 57 | var commitsText = Format(numberOfCommits == 1 ? "{0} commit" : "{0} commits", numberOfCommits); 58 | stringBuilder.Append($"As part of this release we had [{commitsText}]({commitsLink})."); 59 | } 60 | stringBuilder.AppendLine(); 61 | 62 | stringBuilder.AppendLine(targetMilestone.Description); 63 | stringBuilder.AppendLine(); 64 | 65 | AddIssues(stringBuilder, issues); 66 | 67 | await AddFooter(stringBuilder); 68 | 69 | return stringBuilder.ToString(); 70 | } 71 | 72 | Milestone GetPreviousMilestone(Milestone targetMilestone, IReadOnlyList milestones) 73 | { 74 | var currentVersion = targetMilestone.Version(); 75 | return milestones 76 | .OrderByDescending(m => m.Version()) 77 | .Distinct().ToList() 78 | .SkipWhile(x => x.Version() >= currentVersion) 79 | .FirstOrDefault(); 80 | } 81 | 82 | string GetCommitsLink(Milestone targetMilestone, Milestone previousMilestone) 83 | { 84 | if (previousMilestone == null) 85 | { 86 | return $"https://github.com/{user}/{repository}/commits/{targetMilestone.Title}"; 87 | } 88 | return $"https://github.com/{user}/{repository}/compare/{previousMilestone.Title}...{targetMilestone.Title}"; 89 | } 90 | 91 | void AddIssues(StringBuilder builder, List issues) 92 | { 93 | var bugs = issues 94 | .Where(issue => issue.IsBug()) 95 | .ToList(); 96 | 97 | if (bugs.Any()) 98 | { 99 | PrintHeading("Bugs", builder); 100 | 101 | PrintIssue(builder, bugs); 102 | 103 | builder.AppendLine(); 104 | } 105 | 106 | var others = issues.Where(issue =>!issue.IsBug()) 107 | .ToList(); 108 | 109 | if (others.Any()) 110 | { 111 | PrintHeading("Improvements/Features", builder); 112 | 113 | PrintIssue(builder, others); 114 | 115 | builder.AppendLine(); 116 | } 117 | } 118 | 119 | static async Task AddFooter(StringBuilder stringBuilder) 120 | { 121 | var file = new FileInfo("footer.md"); 122 | 123 | if (!file.Exists) 124 | { 125 | file = new FileInfo("footer.txt"); 126 | } 127 | 128 | if (!file.Exists) 129 | { 130 | stringBuilder.Append(@"## Where to get it 131 | You can download this release from [nuget](https://www.nuget.org/profiles/nservicebus/)"); 132 | return; 133 | } 134 | 135 | using (var reader = file.OpenText()) 136 | { 137 | stringBuilder.Append(await reader.ReadToEndAsync()); 138 | } 139 | } 140 | 141 | static void PrintHeading(string labelName, StringBuilder builder) 142 | { 143 | builder.AppendFormat($"__{labelName}__\r\n"); 144 | } 145 | 146 | static void PrintIssue(StringBuilder builder, List relevantIssues) 147 | { 148 | foreach (var issue in relevantIssues) 149 | { 150 | builder.Append($"- [__#{issue.Number}__]({issue.HtmlUrl}) {issue.Title}\r\n"); 151 | } 152 | } 153 | 154 | IGitHubClient gitHubClient; 155 | string user; 156 | string repository; 157 | string milestoneTitle; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/App/ReleaseNotesCompiler.CLI.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {F1163F09-3D4E-4F95-AF46-24C15AB297FB} 8 | Exe 9 | Properties 10 | ReleaseNotesCompiler.CLI 11 | ReleaseNotesCompiler.CLI 12 | v4.6 13 | 512 14 | 15 | 16 | ..\ 17 | true 18 | 19 | 20 | 21 | AnyCPU 22 | true 23 | full 24 | false 25 | bin\Debug\ 26 | DEBUG;TRACE 27 | prompt 28 | 4 29 | 30 | 31 | AnyCPU 32 | pdbonly 33 | true 34 | bin\Release\ 35 | TRACE 36 | prompt 37 | 4 38 | 39 | 40 | 41 | ..\packages\CommandLineParser.1.9.71\lib\net45\CommandLine.dll 42 | 43 | 44 | ..\packages\Octokit.0.22.0\lib\net45\Octokit.dll 45 | True 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | Designer 63 | 64 | 65 | 66 | 67 | {b02a026e-ca3a-48f4-bba9-eb337b0a2035} 68 | ReleaseNotesCompiler 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | (); 92 | var attribute = config.Attribute("ExcludeAssemblies"); 93 | if (attribute != null) 94 | foreach (var item in attribute.Value.Split('|').Select(x => x.Trim()).Where(x => x != string.Empty)) 95 | excludedAssemblies.Add(item); 96 | var element = config.Element("ExcludeAssemblies"); 97 | if (element != null) 98 | foreach (var item in element.Value.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).Where(x => x != string.Empty)) 99 | excludedAssemblies.Add(item); 100 | 101 | var filesToCleanup = Files.Select(f => f.ItemSpec).Where(f => !excludedAssemblies.Contains(Path.GetFileNameWithoutExtension(f), StringComparer.InvariantCultureIgnoreCase)); 102 | 103 | foreach (var item in filesToCleanup) 104 | File.Delete(item); 105 | ]]> 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /src/App/Program.cs: -------------------------------------------------------------------------------- 1 | namespace ReleaseNotesCompiler.CLI 2 | { 3 | using System; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using CommandLine; 8 | using CommandLine.Text; 9 | using Octokit; 10 | using FileMode = System.IO.FileMode; 11 | 12 | abstract class CommonSubOptions 13 | { 14 | [Option('u', "username", HelpText = "The username to access GitHub with.", Required = true)] 15 | public string Username { get; set; } 16 | 17 | [Option('p', "password", HelpText = "The password to access GitHub with.", Required = true)] 18 | public string Password { get; set; } 19 | 20 | [Option('o', "owner", HelpText = "The owner of the repository.", Required = true)] 21 | public string RepositoryOwner { get; set; } 22 | 23 | [Option('r', "repository", HelpText = "The name of the repository.", Required = true)] 24 | public string RepositoryName { get; set; } 25 | 26 | [Option('m', "milestone", HelpText = "The milestone to use.", Required = true)] 27 | public string Milestone { get; set; } 28 | 29 | public GitHubClient CreateGitHubClient() 30 | { 31 | var creds = new Credentials(Username, Password); 32 | var github = new GitHubClient(new ProductHeaderValue("ReleaseNotesCompiler")) { Credentials = creds }; 33 | 34 | return github; 35 | } 36 | } 37 | 38 | class CreateSubOptions : CommonSubOptions 39 | { 40 | [Option('a', "asset", HelpText = "Path to the file to include in the release.", Required = false)] 41 | public string AssetPath { get; set; } 42 | 43 | [Option('t', "targetcommitish", HelpText = "The commit to tag. Can be a branch or SHA. Defaults to repo's default branch.", Required = false)] 44 | public string TargetCommitish { get; set; } 45 | } 46 | 47 | class AttachSubOptions : CommonSubOptions 48 | { 49 | [Option('a', "asset", HelpText = "Path to the file to include in the release.", Required = false)] 50 | public string AssetPath { get; set; } 51 | } 52 | 53 | class PublishSubOptions : CommonSubOptions 54 | { 55 | } 56 | 57 | class Options 58 | { 59 | [VerbOption("create", HelpText = "Creates a draft release notes from a milestone.")] 60 | public CreateSubOptions CreateVerb { get; set; } 61 | 62 | [VerbOption("attach", HelpText = "Attaches an asset to a release.")] 63 | public AttachSubOptions AttachVerb { get; set; } 64 | 65 | [VerbOption("publish", HelpText = "Publishes the release notes and closes the milestone.")] 66 | public PublishSubOptions PublishVerb { get; set; } 67 | 68 | [HelpVerbOption] 69 | public string DoHelpForVerb(string verbName) 70 | { 71 | return HelpText.AutoBuild(this, verbName); 72 | } 73 | } 74 | 75 | class Program 76 | { 77 | static int Main(string[] args) 78 | { 79 | var options = new Options(); 80 | 81 | var result = 1; 82 | 83 | if (!Parser.Default.ParseArgumentsStrict(args, options, (verb, subOptions) => 84 | { 85 | if (verb == "create") 86 | { 87 | result = CreateReleaseAsync((CreateSubOptions)subOptions).Result; 88 | } 89 | 90 | if (verb == "attach") 91 | { 92 | result = AttachToReleaseAsync((AttachSubOptions)subOptions).Result; 93 | } 94 | 95 | if (verb == "publish") 96 | { 97 | result = PublishReleaseAsync((PublishSubOptions)subOptions).Result; 98 | } 99 | })) 100 | { 101 | return 1; 102 | } 103 | 104 | return result; 105 | } 106 | 107 | static async Task CreateReleaseAsync(CreateSubOptions options) 108 | { 109 | try 110 | { 111 | var github = options.CreateGitHubClient(); 112 | 113 | await CreateRelease(github, options.RepositoryOwner, options.RepositoryName, options.Milestone, options.TargetCommitish, options.AssetPath); 114 | 115 | return 0; 116 | } 117 | catch (Exception ex) 118 | { 119 | Console.WriteLine(ex); 120 | 121 | return 1; 122 | } 123 | } 124 | 125 | static async Task AttachToReleaseAsync(AttachSubOptions options) 126 | { 127 | try 128 | { 129 | var github = options.CreateGitHubClient(); 130 | 131 | await AttachToRelease(github, options.RepositoryOwner, options.RepositoryName, options.Milestone, options.AssetPath); 132 | 133 | return 0; 134 | } 135 | catch (Exception ex) 136 | { 137 | Console.WriteLine(ex); 138 | 139 | return 1; 140 | } 141 | } 142 | 143 | static async Task PublishReleaseAsync(PublishSubOptions options) 144 | { 145 | try 146 | { 147 | var github = options.CreateGitHubClient(); 148 | 149 | await CloseMilestone(github, options.RepositoryOwner, options.RepositoryName, options.Milestone); 150 | 151 | await PublishRelease(github, options.RepositoryOwner, options.RepositoryName, options.Milestone); 152 | 153 | return 0; 154 | } 155 | catch (Exception ex) 156 | { 157 | Console.WriteLine(ex); 158 | 159 | return 1; 160 | } 161 | } 162 | 163 | static async Task CreateRelease(GitHubClient github, string owner, string repository, string milestone, string targetCommitish, string asset) 164 | { 165 | var releaseNotesBuilder = new ReleaseNotesBuilder(new DefaultGitHubClient(github, owner, repository), owner, repository, milestone); 166 | 167 | var result = await releaseNotesBuilder.BuildReleaseNotes(); 168 | 169 | var releaseUpdate = new NewRelease(milestone) 170 | { 171 | Draft = true, 172 | Body = result, 173 | Name = milestone, 174 | }; 175 | if (!string.IsNullOrEmpty(targetCommitish)) 176 | releaseUpdate.TargetCommitish = targetCommitish; 177 | 178 | var release = await github.Repository.Release.Create(owner, repository, releaseUpdate); 179 | 180 | if (File.Exists(asset)) 181 | { 182 | var upload = new ReleaseAssetUpload { FileName = Path.GetFileName(asset), ContentType = "application/octet-stream", RawData = File.Open(asset, FileMode.Open) }; 183 | 184 | await github.Repository.Release.UploadAsset(release, upload); 185 | } 186 | } 187 | 188 | static async Task AttachToRelease(GitHubClient github, string owner, string repository, string milestone, string asset) 189 | { 190 | if (!File.Exists(asset)) 191 | return; 192 | 193 | var releases = await github.Repository.Release.GetAll(owner, repository); 194 | var release = releases.FirstOrDefault(r => r.Name == milestone); 195 | if (release == null) 196 | return; 197 | 198 | var upload = new ReleaseAssetUpload { FileName = Path.GetFileName(asset), ContentType = "application/octet-stream", RawData = File.Open(asset, FileMode.Open) }; 199 | 200 | await github.Repository.Release.UploadAsset(release, upload); 201 | } 202 | 203 | static async Task CloseMilestone(GitHubClient github, string owner, string repository, string milestoneTitle) 204 | { 205 | var milestoneClient = github.Issue.Milestone; 206 | var openMilestones = await milestoneClient.GetAllForRepository(owner, repository, new MilestoneRequest { State = ItemStateFilter.Open }); 207 | var milestone = openMilestones.FirstOrDefault(m => m.Title == milestoneTitle); 208 | if (milestone == null) 209 | return; 210 | 211 | await milestoneClient.Update(owner, repository, milestone.Number, new MilestoneUpdate { State = ItemState.Closed }); 212 | } 213 | 214 | static async Task PublishRelease(GitHubClient github, string owner, string repository, string milestone) 215 | { 216 | var releases = await github.Repository.Release.GetAll(owner, repository); 217 | var release = releases.FirstOrDefault(r => r.Name == milestone); 218 | if (release == null) 219 | return; 220 | 221 | var releaseUpdate = new ReleaseUpdate 222 | { 223 | Draft = false 224 | }; 225 | 226 | await github.Repository.Release.Edit(owner, repository, release.Id, releaseUpdate); 227 | } 228 | } 229 | } -------------------------------------------------------------------------------- /src/GitHubReleaseNotes.sln.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | True 5 | True 6 | True 7 | False 8 | SOLUTION 9 | DO_NOT_SHOW 10 | ERROR 11 | DO_NOT_SHOW 12 | DO_NOT_SHOW 13 | DO_NOT_SHOW 14 | DO_NOT_SHOW 15 | ERROR 16 | ERROR 17 | ERROR 18 | ERROR 19 | ERROR 20 | DO_NOT_SHOW 21 | DO_NOT_SHOW 22 | DO_NOT_SHOW 23 | ERROR 24 | ERROR 25 | ERROR 26 | ERROR 27 | ERROR 28 | ERROR 29 | ERROR 30 | ERROR 31 | ERROR 32 | ERROR 33 | ERROR 34 | ERROR 35 | ERROR 36 | ERROR 37 | ERROR 38 | ERROR 39 | ERROR 40 | ERROR 41 | ERROR 42 | ERROR 43 | ERROR 44 | DO_NOT_SHOW 45 | DO_NOT_SHOW 46 | ERROR 47 | DO_NOT_SHOW 48 | DO_NOT_SHOW 49 | ERROR 50 | ERROR 51 | ERROR 52 | ERROR 53 | ERROR 54 | ERROR 55 | ERROR 56 | ERROR 57 | ERROR 58 | WARNING 59 | ERROR 60 | ERROR 61 | ERROR 62 | ERROR 63 | ERROR 64 | ERROR 65 | ERROR 66 | ERROR 67 | ERROR 68 | ERROR 69 | ERROR 70 | ERROR 71 | ERROR 72 | ERROR 73 | ERROR 74 | SUGGESTION 75 | ERROR 76 | ERROR 77 | ERROR 78 | ERROR 79 | ERROR 80 | ERROR 81 | ERROR 82 | ERROR 83 | ERROR 84 | ERROR 85 | WARNING 86 | ERROR 87 | ERROR 88 | ERROR 89 | DoHide 90 | DoHide 91 | DoHide 92 | DoHide 93 | DoHide 94 | DoHide 95 | DoHide 96 | DoHide 97 | DoHide 98 | DoHide 99 | DoHide 100 | DoHide 101 | DoHide 102 | DoHide 103 | DoHide 104 | DoHide 105 | DoHide 106 | DoHide 107 | DoHide 108 | ERROR 109 | ERROR 110 | ERROR 111 | ERROR 112 | ERROR 113 | ERROR 114 | ERROR 115 | ERROR 116 | ERROR 117 | ERROR 118 | ERROR 119 | DO_NOT_SHOW 120 | SUGGESTION 121 | WARNING 122 | WARNING 123 | ERROR 124 | ERROR 125 | ERROR 126 | ERROR 127 | ERROR 128 | <?xml version="1.0" encoding="utf-16"?><Profile name="Format My Code Using &quot;Particular&quot; conventions"><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSUseVar><BehavourStyle>CAN_CHANGE_TO_IMPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_IMPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_IMPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSReformatCode>True</CSReformatCode><CSReorderTypeMembers>True</CSReorderTypeMembers><JsInsertSemicolon>True</JsInsertSemicolon><JsReformatCode>True</JsReformatCode><CssReformatCode>True</CssReformatCode><CSArrangeThisQualifier>True</CSArrangeThisQualifier><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><HtmlReformatCode>True</HtmlReformatCode><CSShortenReferences>True</CSShortenReferences><CSharpFormatDocComments>True</CSharpFormatDocComments><CssAlphabetizeProperties>True</CssAlphabetizeProperties></Profile> 129 | Default: Reformat Code 130 | Format My Code Using "Particular" conventions 131 | False 132 | False 133 | ALWAYS_ADD 134 | ALWAYS_ADD 135 | ALWAYS_ADD 136 | ALWAYS_ADD 137 | ALWAYS_ADD 138 | False 139 | False 140 | False 141 | CHOP_ALWAYS 142 | False 143 | CHOP_ALWAYS 144 | CHOP_ALWAYS 145 | True 146 | True 147 | <?xml version="1.0" encoding="utf-16"?> 148 | <Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> 149 | <TypePattern Priority="100" DisplayName="Type Pattern"> 150 | <TypePattern.Match> 151 | <Or> 152 | <And> 153 | <Kind Is="Interface" /> 154 | <Or> 155 | <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> 156 | <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> 157 | </Or> 158 | </And> 159 | <HasAttribute Name="System.Runtime.InteropServices.StructLayoutAttribute" /> 160 | </Or> 161 | </TypePattern.Match> 162 | </TypePattern> 163 | <TypePattern Priority="100" DisplayName="Type Pattern"> 164 | <TypePattern.Match> 165 | <And> 166 | <Kind Is="Class" /> 167 | <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /> 168 | </And> 169 | </TypePattern.Match> 170 | <Entry DisplayName="Entry"> 171 | <Entry.Match> 172 | <And> 173 | <Kind Is="Method" /> 174 | <Or> 175 | <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /> 176 | <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /> 177 | <HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" /> 178 | <HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" /> 179 | </Or> 180 | </And> 181 | </Entry.Match> 182 | </Entry> 183 | <Entry DisplayName="Entry" /> 184 | <Entry Priority="100" DisplayName="Entry"> 185 | <Entry.Match> 186 | <And> 187 | <Kind Is="Method" /> 188 | <HasAttribute Name="NUnit.Framework.TestAttribute" /> 189 | </And> 190 | </Entry.Match> 191 | <Entry.SortBy> 192 | <Name /> 193 | </Entry.SortBy> 194 | </Entry> 195 | </TypePattern> 196 | <TypePattern DisplayName="Type Pattern"> 197 | <Entry Priority="100" DisplayName="Entry"> 198 | <Entry.Match> 199 | <And> 200 | <Access Is="Public" /> 201 | <Kind Is="Delegate" /> 202 | </And> 203 | </Entry.Match> 204 | <Entry.SortBy> 205 | <Name /> 206 | </Entry.SortBy> 207 | </Entry> 208 | <Entry Priority="100" DisplayName="Entry"> 209 | <Entry.Match> 210 | <And> 211 | <Access Is="Public" /> 212 | <Kind Is="Enum" /> 213 | </And> 214 | </Entry.Match> 215 | <Entry.SortBy> 216 | <Name /> 217 | </Entry.SortBy> 218 | </Entry> 219 | <Entry DisplayName="Entry"> 220 | <Entry.Match> 221 | <Kind Is="Constructor" /> 222 | </Entry.Match> 223 | <Entry.SortBy> 224 | <Static /> 225 | </Entry.SortBy> 226 | </Entry> 227 | <Entry DisplayName="Entry"> 228 | <Entry.Match> 229 | <Or> 230 | <Kind Is="Property" /> 231 | <Kind Is="Indexer" /> 232 | </Or> 233 | </Entry.Match> 234 | </Entry> 235 | <Entry Priority="100" DisplayName="Entry"> 236 | <Entry.Match> 237 | <And> 238 | <Kind Is="Member" /> 239 | <ImplementsInterface /> 240 | </And> 241 | </Entry.Match> 242 | <Entry.SortBy> 243 | <ImplementsInterface Immediate="True" /> 244 | </Entry.SortBy> 245 | </Entry> 246 | <Entry DisplayName="Entry" /> 247 | <Entry DisplayName="Entry"> 248 | <Entry.Match> 249 | <Or> 250 | <Kind Is="Constant" /> 251 | <And> 252 | <Kind Is="Field" /> 253 | <Static /> 254 | </And> 255 | </Or> 256 | </Entry.Match> 257 | <Entry.SortBy> 258 | <Kind Order="Constant Field" /> 259 | </Entry.SortBy> 260 | </Entry> 261 | <Entry DisplayName="Entry"> 262 | <Entry.Match> 263 | <And> 264 | <Kind Is="Field" /> 265 | <Not> 266 | <Static /> 267 | </Not> 268 | </And> 269 | </Entry.Match> 270 | <Entry.SortBy> 271 | <Readonly /> 272 | <Name /> 273 | </Entry.SortBy> 274 | </Entry> 275 | <Entry DisplayName="Entry"> 276 | <Entry.Match> 277 | <Kind Is="Type" /> 278 | </Entry.Match> 279 | <Entry.SortBy> 280 | <Name /> 281 | </Entry.SortBy> 282 | </Entry> 283 | </TypePattern> 284 | </Patterns> 285 | <?xml version="1.0" encoding="utf-8" ?> 286 | 287 | <!-- 288 | I. Overall 289 | 290 | I.1 Each pattern can have <Match>....</Match> element. For the given type declaration, the pattern with the match, evaluated to 'true' with the largest weight, will be used 291 | I.2 Each pattern consists of the sequence of <Entry>...</Entry> elements. Type member declarations are distributed between entries 292 | I.3 If pattern has RemoveAllRegions="true" attribute, then all regions will be cleared prior to reordering. Otherwise, only auto-generated regions will be cleared 293 | I.4 The contents of each entry is sorted by given keys (First key is primary, next key is secondary, etc). Then the declarations are grouped and en-regioned by given property 294 | 295 | II. Available match operands 296 | 297 | Each operand may have Weight="..." attribute. This weight will be added to the match weight if the operand is evaluated to 'true'. 298 | The default weight is 1 299 | 300 | II.1 Boolean functions: 301 | II.1.1 <And>....</And> 302 | II.1.2 <Or>....</Or> 303 | II.1.3 <Not>....</Not> 304 | 305 | II.2 Operands 306 | II.2.1 <Kind Is="..."/>. Kinds are: class, struct, interface, enum, delegate, type, constructor, destructor, property, indexer, method, operator, field, constant, event, member 307 | II.2.2 <Name Is="..." [IgnoreCase="true/false"] />. The 'Is' attribute contains regular expression 308 | II.2.3 <HasAttribute CLRName="..." [Inherit="true/false"] />. The 'CLRName' attribute contains regular expression 309 | II.2.4 <Access Is="..."/>. The 'Is' values are: public, protected, internal, protected internal, private 310 | II.2.5 <Static/> 311 | II.2.6 <Abstract/> 312 | II.2.7 <Virtual/> 313 | II.2.8 <Override/> 314 | II.2.9 <Sealed/> 315 | II.2.10 <Readonly/> 316 | II.2.11 <ImplementsInterface CLRName="..."/>. The 'CLRName' attribute contains regular expression 317 | II.2.12 <HandlesEvent /> 318 | --> 319 | 320 | <Patterns xmlns="urn:shemas-jetbrains-com:member-reordering-patterns"> 321 | 322 | <!--Do not reorder COM interfaces and structs marked by StructLayout attribute--> 323 | <Pattern> 324 | <Match> 325 | <Or Weight="100"> 326 | <And> 327 | <Kind Is="interface"/> 328 | <Or> 329 | <HasAttribute CLRName="System.Runtime.InteropServices.InterfaceTypeAttribute"/> 330 | <HasAttribute CLRName="System.Runtime.InteropServices.ComImport"/> 331 | </Or> 332 | </And> 333 | <HasAttribute CLRName="System.Runtime.InteropServices.StructLayoutAttribute"/> 334 | </Or> 335 | </Match> 336 | </Pattern> 337 | 338 | <!--Special formatting of NUnit test fixture--> 339 | <Pattern RemoveAllRegions="true"> 340 | <Match> 341 | <And Weight="100"> 342 | <Kind Is="class"/> 343 | <HasAttribute CLRName="NUnit.Framework.TestFixtureAttribute" Inherit="true"/> 344 | </And> 345 | </Match> 346 | 347 | <!--Setup/Teardow--> 348 | <Entry> 349 | <Match> 350 | <And> 351 | <Kind Is="method"/> 352 | <Or> 353 | <HasAttribute CLRName="NUnit.Framework.SetUpAttribute" Inherit="true"/> 354 | <HasAttribute CLRName="NUnit.Framework.TearDownAttribute" Inherit="true"/> 355 | <HasAttribute CLRName="NUnit.Framework.FixtureSetUpAttribute" Inherit="true"/> 356 | <HasAttribute CLRName="NUnit.Framework.FixtureTearDownAttribute" Inherit="true"/> 357 | </Or> 358 | </And> 359 | </Match> 360 | </Entry> 361 | 362 | <!--All other members--> 363 | <Entry/> 364 | 365 | <!--Test methods--> 366 | <Entry> 367 | <Match> 368 | <And Weight="100"> 369 | <Kind Is="method"/> 370 | <HasAttribute CLRName="NUnit.Framework.TestAttribute" Inherit="false"/> 371 | </And> 372 | </Match> 373 | <Sort> 374 | <Name/> 375 | </Sort> 376 | </Entry> 377 | </Pattern> 378 | 379 | <!--Default pattern--> 380 | <Pattern> 381 | 382 | <!--public delegate--> 383 | <Entry> 384 | <Match> 385 | <And Weight="100"> 386 | <Access Is="public"/> 387 | <Kind Is="delegate"/> 388 | </And> 389 | </Match> 390 | <Sort> 391 | <Name/> 392 | </Sort> 393 | </Entry> 394 | 395 | <!--public enum--> 396 | <Entry> 397 | <Match> 398 | <And Weight="100"> 399 | <Access Is="public"/> 400 | <Kind Is="enum"/> 401 | </And> 402 | </Match> 403 | <Sort> 404 | <Name/> 405 | </Sort> 406 | </Entry> 407 | 408 | <!--Constructors. Place static one first--> 409 | <Entry> 410 | <Match> 411 | <Kind Is="constructor"/> 412 | </Match> 413 | <Sort> 414 | <Static/> 415 | </Sort> 416 | </Entry> 417 | 418 | <!--properties, indexers--> 419 | <Entry> 420 | <Match> 421 | <Or> 422 | <Kind Is="property"/> 423 | <Kind Is="indexer"/> 424 | </Or> 425 | </Match> 426 | </Entry> 427 | 428 | <!--interface implementations--> 429 | <Entry> 430 | <Match> 431 | <And Weight="100"> 432 | <Kind Is="member"/> 433 | <ImplementsInterface/> 434 | </And> 435 | </Match> 436 | <Sort> 437 | <ImplementsInterface Immediate="true"/> 438 | </Sort> 439 | </Entry> 440 | 441 | <!--all other members--> 442 | <Entry/> 443 | 444 | <!--static fields and constants--> 445 | <Entry> 446 | <Match> 447 | <Or> 448 | <Kind Is="constant"/> 449 | <And> 450 | <Kind Is="field"/> 451 | <Static/> 452 | </And> 453 | </Or> 454 | </Match> 455 | <Sort> 456 | <Kind Order="constant field"/> 457 | </Sort> 458 | </Entry> 459 | 460 | <!--instance fields--> 461 | <Entry> 462 | <Match> 463 | <And> 464 | <Kind Is="field"/> 465 | <Not> 466 | <Static/> 467 | </Not> 468 | </And> 469 | </Match> 470 | <Sort> 471 | <Readonly/> 472 | <Name/> 473 | </Sort> 474 | </Entry> 475 | 476 | <!--nested types--> 477 | <Entry> 478 | <Match> 479 | <Kind Is="type"/> 480 | </Match> 481 | <Sort> 482 | <Name/> 483 | </Sort> 484 | </Entry> 485 | </Pattern> 486 | 487 | </Patterns> 488 | 489 | CustomLayout 490 | True 491 | True 492 | True 493 | False 494 | True 495 | False 496 | False 497 | False 498 | True 499 | Automatic property 500 | True 501 | False 502 | False 503 | DB 504 | DTC 505 | ID 506 | NSB 507 | SLA 508 | $object$_On$event$ 509 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 510 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 511 | <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /> 512 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 513 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 514 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 515 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 516 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 517 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 518 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 519 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 520 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 521 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 522 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 523 | <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /> 524 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 525 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 526 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 527 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 528 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 529 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 530 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 531 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 532 | $object$_On$event$ 533 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 534 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 535 | <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /> 536 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 537 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 538 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 539 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 540 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 541 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 542 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 543 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 544 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 545 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 546 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 547 | <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /> 548 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 549 | True 550 | True 551 | True 552 | True 553 | True 554 | True 555 | True 556 | 557 | 558 | 559 | 560 | <data /> 561 | <data><IncludeFilters /><ExcludeFilters /></data> --------------------------------------------------------------------------------