Skip to content

Instantly share code, notes, and snippets.

@maxkatz6
Last active July 9, 2022 12:42
Show Gist options
  • Save maxkatz6/1765fe9e5e0c2f09d9da5c9bc9ef4471 to your computer and use it in GitHub Desktop.
Save maxkatz6/1765fe9e5e0c2f09d9da5c9bc9ef4471 to your computer and use it in GitHub Desktop.
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) { }
}
<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