Skip to content

(#89) Add ability to populate release notes with information about contributors #541

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="coverlet.msbuild" Version="6.0.4" />
<PackageVersion Include="Destructurama.Attributed" Version="5.1.0" />
<PackageVersion Include="GraphQL.Client" Version="6.0.1" />
<PackageVersion Include="GraphQL.Client.Serializer.SystemTextJson" Version="6.0.1" />
<PackageVersion Include="IDisposableAnalyzers" Version="4.0.8" />
<PackageVersion Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.2" />
Expand Down
8 changes: 8 additions & 0 deletions src/GitReleaseManager.Cli/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
using GitReleaseManager.Core.Provider;
using GitReleaseManager.Core.ReleaseNotes;
using GitReleaseManager.Core.Templates;
using GraphQL.Client.Http;
using GraphQL.Client.Serializer.SystemTextJson;
using Microsoft.Extensions.DependencyInjection;
using NGitLab;
using Octokit;
Expand Down Expand Up @@ -211,6 +213,12 @@ private static void RegisterVcsProvider(BaseVcsOptions vcsOptions, IServiceColle
// default to Github
serviceCollection
.AddSingleton<IGitHubClient>((_) => new GitHubClient(new ProductHeaderValue("GitReleaseManager")) { Credentials = new Credentials(vcsOptions.Token) })
.AddSingleton<GraphQL.Client.Abstractions.IGraphQLClient>(_ =>
{
var client = new GraphQLHttpClient(new GraphQLHttpClientOptions { EndPoint = new Uri("https://api.github.com/graphql") }, new SystemTextJsonSerializer());
client.HttpClient.DefaultRequestHeaders.Add("Authorization", $"bearer {vcsOptions.Token}");
return client;
})
.AddSingleton<IVcsProvider, GitHubProvider>();
}
}
Expand Down
1 change: 1 addition & 0 deletions src/GitReleaseManager.Core/Configuration/Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public Config()
ShaSectionHeading = "SHA256 Hashes of the release artifacts",
ShaSectionLineFormat = "- `{1}\t{0}`",
AllowUpdateToPublishedRelease = false,
IncludeContributors = false,
};

Export = new ExportConfig
Expand Down
3 changes: 3 additions & 0 deletions src/GitReleaseManager.Core/Configuration/CreateConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,8 @@ public class CreateConfig

[YamlMember(Alias = "allow-update-to-published")]
public bool AllowUpdateToPublishedRelease { get; set; }

[YamlMember(Alias = "include-contributors")]
public bool IncludeContributors { get; set; }
}
}
84 changes: 84 additions & 0 deletions src/GitReleaseManager.Core/Extensions/JsonExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;

namespace GitReleaseManager.Core.Extensions
{
internal static class JsonExtensions
{
/// <summary>
/// Get a JsonElement from a path. Each level in the path is seperated by a dot.
/// </summary>
/// <param name="jsonElement">The parent Json element.</param>
/// <param name="path">The path of the desired child element.</param>
/// <returns>The child element.</returns>
public static JsonElement GetJsonElement(this JsonElement jsonElement, string path)
{
if (jsonElement.ValueKind is JsonValueKind.Null || jsonElement.ValueKind is JsonValueKind.Undefined)
{
return default(JsonElement);
}

string[] segments = path.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries);

foreach (var segment in segments)
{
if (int.TryParse(segment, out var index) && jsonElement.ValueKind == JsonValueKind.Array)
{
jsonElement = jsonElement.EnumerateArray().ElementAtOrDefault(index);
if (jsonElement.ValueKind is JsonValueKind.Null || jsonElement.ValueKind is JsonValueKind.Undefined)
{
return default(JsonElement);
}

continue;
}

jsonElement = jsonElement.TryGetProperty(segment, out var value) ? value : default;

if (jsonElement.ValueKind is JsonValueKind.Null || jsonElement.ValueKind is JsonValueKind.Undefined)
{
return default(JsonElement);
}
}

return jsonElement;
}

/// <summary>
/// Get the first JsonElement matching a path from the provided list of paths.
/// </summary>
/// <param name="jsonElement">The parent Json element.</param>
/// <param name="paths">The path of the desired child element.</param>
/// <returns>The child element.</returns>
public static JsonElement GetFirstJsonElement(this JsonElement jsonElement, IEnumerable<string> paths)
{
if (jsonElement.ValueKind is JsonValueKind.Null || jsonElement.ValueKind is JsonValueKind.Undefined)
{
return default(JsonElement);
}

var element = default(JsonElement);

foreach (var path in paths)
{
element = jsonElement.GetJsonElement(path);

if (element.ValueKind is JsonValueKind.Null || element.ValueKind is JsonValueKind.Undefined)
{
continue;
}

break;
}

return element;
}

public static string GetJsonElementValue(this JsonElement jsonElement) => jsonElement.ValueKind != JsonValueKind.Null &&
jsonElement.ValueKind != JsonValueKind.Undefined
? jsonElement.ToString()
: default;
}
}
2 changes: 2 additions & 0 deletions src/GitReleaseManager.Core/GitReleaseManager.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
<ItemGroup>
<PackageReference Include="CommandLineParser" />
<PackageReference Include="Destructurama.Attributed" />
<PackageReference Include="GraphQL.Client" />
<PackageReference Include="GraphQL.Client.Serializer.SystemTextJson" />
<PackageReference Include="Microsoft.SourceLink.GitHub">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
Expand Down
28 changes: 27 additions & 1 deletion src/GitReleaseManager.Core/MappingProfiles/GitHubProfile.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Text.Json;
using AutoMapper;
using GitReleaseManager.Core.Extensions;

Expand All @@ -8,10 +9,11 @@ public class GitHubProfile : Profile
{
public GitHubProfile()
{
// These mappings convert the result of Octokit queries to model classes
CreateMap<Octokit.Issue, Model.Issue>()
.ForMember(dest => dest.PublicNumber, act => act.MapFrom(src => src.Number))
.ForMember(dest => dest.InternalNumber, act => act.MapFrom(src => src.Id))
.ForMember(dest => dest.IsPullRequest, act => act.MapFrom(src => src.HtmlUrl.IndexOf("/pull/", StringComparison.OrdinalIgnoreCase) >= 0))
.ForMember(dest => dest.IsPullRequest, act => act.MapFrom(src => src.HtmlUrl.Contains("/pull/", StringComparison.OrdinalIgnoreCase)))
.ReverseMap();
CreateMap<Model.IssueComment, Octokit.IssueComment>().ReverseMap();
CreateMap<Model.ItemState, Octokit.ItemState>().ReverseMap();
Expand All @@ -23,11 +25,35 @@ public GitHubProfile()
CreateMap<Model.ReleaseAssetUpload, Octokit.ReleaseAssetUpload>().ReverseMap();
CreateMap<Model.Label, Octokit.Label>().ReverseMap();
CreateMap<Model.Label, Octokit.NewLabel>().ReverseMap();
CreateMap<Model.User, Octokit.User>().ReverseMap();
CreateMap<Model.Milestone, Octokit.Milestone>();
CreateMap<Octokit.Milestone, Model.Milestone>()
.ForMember(dest => dest.PublicNumber, act => act.MapFrom(src => src.Number))
.ForMember(dest => dest.InternalNumber, act => act.MapFrom(src => src.Number))
.AfterMap((src, dest) => dest.Version = src.Version());

// These mappings convert the result of GraphQL queries to model classes
CreateMap<JsonElement, Model.Issue>()
.ForMember(dest => dest.PublicNumber, act => act.MapFrom(src => src.GetProperty("number").GetInt32()))
.ForMember(dest => dest.InternalNumber, act => act.MapFrom(src => -1)) // Not available in graphQL (there's a "id" property but it contains a string which represents the Node ID of the object).
.ForMember(dest => dest.Title, act => act.MapFrom(src => src.GetProperty("title").GetString()))
.ForMember(dest => dest.HtmlUrl, act => act.MapFrom(src => src.GetProperty("url").GetString()))
.ForMember(dest => dest.IsPullRequest, act => act.MapFrom(src => src.GetProperty("url").GetString().Contains("/pull/", StringComparison.OrdinalIgnoreCase)))
.ForMember(dest => dest.User, act => act.MapFrom(src => src.GetProperty("author")))
.ForMember(dest => dest.Labels, act => act.MapFrom(src => src.GetJsonElement("labels.nodes").EnumerateArray()))
.ReverseMap();

CreateMap<JsonElement, Model.Label>()
.ForMember(dest => dest.Name, act => act.MapFrom(src => src.GetProperty("name").GetString()))
.ForMember(dest => dest.Color, act => act.MapFrom(src => src.GetProperty("color").GetString()))
.ForMember(dest => dest.Description, act => act.MapFrom(src => src.GetProperty("description").GetString()))
.ReverseMap();

CreateMap<JsonElement, Model.User>()
.ForMember(dest => dest.Login, act => act.MapFrom(src => src.GetProperty("login").GetString()))
.ForMember(dest => dest.HtmlUrl, act => act.MapFrom(src => $"https://github.com{src.GetProperty("resourcePath").GetString()}")) // The resourcePath contains a value similar to "/jericho". That's why we must manually prepend "https://github.com
.ForMember(dest => dest.AvatarUrl, act => act.MapFrom(src => src.GetProperty("avatarUrl").GetString()))
.ReverseMap();
}
}
}
12 changes: 12 additions & 0 deletions src/GitReleaseManager.Core/MappingProfiles/GitLabProfile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@ public GitLabProfile()
.ForMember(dest => dest.PublicNumber, act => act.MapFrom(src => src.IssueId))
.ForMember(dest => dest.HtmlUrl, act => act.MapFrom(src => src.WebUrl))
.ForMember(dest => dest.IsPullRequest, act => act.MapFrom(src => false))
.ForMember(dest => dest.User, act => act.MapFrom(src => src.Author))
.ReverseMap();
CreateMap<NGitLab.Models.MergeRequest, Model.Issue>()
.ForMember(dest => dest.InternalNumber, act => act.MapFrom(src => src.Id))
.ForMember(dest => dest.PublicNumber, act => act.MapFrom(src => src.Iid))
.ForMember(dest => dest.HtmlUrl, act => act.MapFrom(src => src.WebUrl))
.ForMember(dest => dest.IsPullRequest, act => act.MapFrom(src => true))
.ForMember(dest => dest.User, act => act.MapFrom(src => src.Author))
.ReverseMap();
CreateMap<string, Model.Label>().ForMember(dest => dest.Name, act => act.MapFrom(src => src));
CreateMap<Model.Release, NGitLab.Models.ReleaseCreate>()
Expand All @@ -43,6 +45,16 @@ public GitLabProfile()
.ReverseMap();
CreateMap<NGitLab.Models.MergeRequestComment, Model.IssueComment>()
.ReverseMap();
CreateMap<Model.User, NGitLab.Models.User>()
.ForMember(dest => dest.Username, act => act.MapFrom(src => src.Login))
.ForMember(dest => dest.WebURL, act => act.MapFrom(src => src.HtmlUrl))
.ForMember(dest => dest.AvatarURL, act => act.MapFrom(src => src.AvatarUrl))
.ReverseMap();
CreateMap<Model.User, NGitLab.Models.Author>()
.ForMember(dest => dest.Username, act => act.MapFrom(src => src.Login))
.ForMember(dest => dest.WebUrl, act => act.MapFrom(src => src.HtmlUrl))
.ForMember(dest => dest.AvatarUrl, act => act.MapFrom(src => src.AvatarUrl))
.ReverseMap();
}
}
}
4 changes: 4 additions & 0 deletions src/GitReleaseManager.Core/Model/Issue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,14 @@

public int PublicNumber { get; set; }

public string HtmlUrl { get; set; }

Check warning on line 13 in src/GitReleaseManager.Core/Model/Issue.cs

View workflow job for this annotation

GitHub Actions / build (windows-2022)

Change the type of property 'Issue.HtmlUrl' from 'string' to 'System.Uri' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1056)

Check warning on line 13 in src/GitReleaseManager.Core/Model/Issue.cs

View workflow job for this annotation

GitHub Actions / build (windows-2022)

Change the type of property 'Issue.HtmlUrl' from 'string' to 'System.Uri' (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1056)

public IReadOnlyList<Label> Labels { get; set; }

public bool IsPullRequest { get; set; }

public User User { get; set; }

public IReadOnlyList<Issue> LinkedIssues { get; set; }
}
}
5 changes: 5 additions & 0 deletions src/GitReleaseManager.Core/Model/IssueComment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,10 @@ public class IssueComment
/// Gets or sets details about the issue comment.
/// </summary>
public string Body { get; set; }

/// <summary>
/// Gets or sets information about the user who made the comment.
/// </summary>
public User User { get; set; }
}
}
11 changes: 11 additions & 0 deletions src/GitReleaseManager.Core/Model/User.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace GitReleaseManager.Core.Model
{
public sealed class User
{
public string Login { get; set; }

public string HtmlUrl { get; set; }

public string AvatarUrl { get; set; }
}
}
Loading
Loading