Last active
July 9, 2022 12:42
-
-
Save maxkatz6/1765fe9e5e0c2f09d9da5c9bc9ef4471 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Octokit; | |
using System.CommandLine.Invocation; | |
using System.CommandLine; | |
using System.Globalization; | |
using System; | |
using System.Linq; | |
using System.Collections.Generic; | |
using System.Text.RegularExpressions; | |
using System.Threading.Tasks; | |
var groupLabelsInOrder = new (string label, string title)[] | |
{ | |
("api", "New features/APIs"), | |
//("enhancement", "Enhancements"), | |
//("perf", "Performance"), | |
("area-dev-tools", "Dev-Tools"), | |
("bug", "Bugfixes"), | |
("bugfix", "Bugfixes"), | |
("fix", "Bugfixes"), | |
("fixes", "Bugfixes"), | |
("dev-tools", "Dev-Tools"), | |
("devtools", "Dev-Tools"), | |
}; | |
var prefixLabels = new (string label, string title)[] | |
{ | |
("breaking-change", "Breaking-Change"), | |
("os-linux", "Linux"), | |
("os-windows", "Windows"), | |
("os-macos", "macOS"), | |
("os-browser", "Browser"), | |
("os-ios", "iOS"), | |
("os-android", "Android"), | |
("area-x11", "Linux"), | |
("win32", "Windows"), | |
("win", "Windows"), | |
("osx", "macOS"), | |
("macos", "macOS"), | |
("browser", "Browser"), | |
("wasm", "Browser"), | |
("ios", "iOS"), | |
("android", "Android") | |
}; | |
var tagsToIgnore = new string[] | |
{ | |
"wont-backport", | |
"backported-0.9" | |
}; | |
var rootCommand = new RootCommand | |
{ | |
new Option<string>( | |
"--org", | |
getDefaultValue: () => "AvaloniaUI"), | |
new Option<string>( | |
"--repo", | |
getDefaultValue: () => "Avalonia"), | |
new Option<string>( | |
"--changes-after", | |
getDefaultValue: () => "0.10.15"), | |
new Option<string>( | |
"--target-branch", | |
getDefaultValue: () => "0.10.16"), | |
new Option<string>( | |
"--auto-token") | |
}; | |
rootCommand.Handler = CommandHandler.Create<string, string, string, string, string?>(async (org, repo, changesAfter, targetBranch, autoToken) => | |
{ | |
var github = autoToken is null | |
? new GitHubClient(new ProductHeaderValue("changelog-gen")) | |
: new GitHubClient(new ProductHeaderValue("changelog-gen"), new Store(autoToken)); | |
var repository = await github.Repository.Get(org, repo); | |
var prevBranch = await github.Repository.Branch.Get(repository.Id, $"release/{changesAfter}"); | |
var currBranch = await github.Repository.Branch.Get(repository.Id, $"release/{targetBranch}"); | |
var prevCommit = await github.Git.Commit.Get(repository.Id, prevBranch.Commit.Sha); | |
var lastCommit = await github.Git.Commit.Get(repository.Id, currBranch.Commit.Sha); | |
// Master PRs backported to the stable. | |
var issues = await github.Search.SearchIssues(new SearchIssuesRequest | |
{ | |
Is = new[] { IssueIsQualifier.Merged, IssueIsQualifier.PullRequest }, | |
Labels = new[] { "backport-candidate" }, | |
Merged = new DateRange(prevCommit.Committer.Date, lastCommit.Committer.Date), | |
PerPage = 200, | |
Repos = { repository.FullName }, | |
}); | |
// Stable PRs | |
var pullRequests = await github.PullRequest | |
.GetAllForRepository(repository.Id, new PullRequestRequest() { Base = "stable/0.10.x", State = ItemStateFilter.Closed }, new ApiOptions { PageSize = 200 }); | |
var ghIssues = issues.Items | |
.Where(i => i.PullRequest is not null).Select(i => new GhIssue(i)) | |
.Concat(pullRequests | |
.Where(pr => pr.Merged && pr.MergedAt >= prevCommit.Committer.Date && pr.MergedAt <= lastCommit.Committer.Date) | |
.Select(p => new GhIssue(p))) | |
.DistinctBy(i => i.Number); | |
foreach (var group in ConvertIssues(ghIssues).GroupBy(i => i.Group)) | |
{ | |
Console.WriteLine($"### {group.Key ?? "Misc"}"); | |
Console.WriteLine(); | |
foreach (var (_, number, prefixes, title, questionable) in group) | |
{ | |
if (questionable) | |
{ | |
Console.Write("??? "); | |
} | |
Console.WriteLine(prefixes.Length > 0 | |
? $"#{number} {prefixes} {title}" | |
: $"#{number} {title}"); | |
} | |
Console.WriteLine(); | |
Console.WriteLine(); | |
} | |
Console.ReadLine(); | |
}); | |
return await rootCommand.InvokeAsync(args); | |
IEnumerable<(string Group, int Number, string Prefixes, string Title, bool Questionable)> ConvertIssues(IEnumerable<GhIssue> issues) | |
{ | |
foreach (var group in issues | |
.Where(item => !item.Labels.Any(l => tagsToIgnore.Contains(l.Name))) | |
.Select(item => ( | |
item, | |
prefix: TransformLabels(item.Labels, prefixLabels).Concat(ParseLabelsFromTitle(item.Title, prefixLabels)) | |
.Select(t => t.title).Distinct().ToArray(), | |
group: TransformLabels(item.Labels, groupLabelsInOrder).Concat(ParseLabelsFromTitle(item.Title, groupLabelsInOrder)).FirstOrDefault() | |
)) | |
.GroupBy(t => t.group.title) | |
.OrderBy(t => groupLabelsInOrder.Select(l => l.title).ToList().IndexOf(t.Key) is var index && index >= 0 ? index : int.MaxValue)) | |
{ | |
foreach (var (item, prefix, _) in group.OrderByDescending(t => t.prefix.Length > 0).ThenBy(t => t.item.Number)) | |
{ | |
var prefixes = string.Concat(prefix.OrderBy(t => prefixLabels | |
.Select(l => l.title).ToList().IndexOf(t) is var index && index >= 0 ? index : int.MaxValue) | |
.Select(p => $"[{p}]")); | |
var hasBackportedLabel = item.Labels.Any(l => l.Name == "backported-0.10.x"); | |
yield return (group.Key ?? "Misc", item.Number, prefixes, item.Title, !hasBackportedLabel); | |
} | |
} | |
} | |
static IEnumerable<(string label, string title)> TransformLabels(IReadOnlyList<Label> labels, (string label, string title)[] transformation) | |
=> transformation.Where(t => labels.Any(l => StringComparer.OrdinalIgnoreCase.Equals(t.label, l.Name))); | |
static IEnumerable<(string label, string title)> ParseLabelsFromTitle(string title, (string label, string title)[] transformation) | |
=> transformation.Where(t => new Regex($@"\b({t.label})\b", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Singleline).IsMatch(title)); | |
record Store(string Token) : ICredentialStore | |
{ | |
public Task<Credentials> GetCredentials() => Task.FromResult(new Credentials(Token)); | |
} | |
record GhIssue(int Number, string Title, IReadOnlyList<Label> Labels) | |
{ | |
public GhIssue(Issue issue) : this(issue.Number, issue.Title, issue.Labels) { } | |
public GhIssue(PullRequest pr) : this(pr.Number, pr.Title, pr.Labels) { } | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<Project Sdk="Microsoft.NET.Sdk"> | |
<PropertyGroup> | |
<OutputType>Exe</OutputType> | |
<TargetFramework>net6.0</TargetFramework> | |
</PropertyGroup> | |
<ItemGroup> | |
<PackageReference Include="Octokit" Version="0.50.0" /> | |
<PackageReference Include="System.CommandLine" Version="2.0.0-beta1.21308.1" /> | |
</ItemGroup> | |
</Project> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment