Skip to content

Commit d86d711

Browse files
authored
Update method GetAllIssuesAndPullRequestsAsync (#27)
* - Separate clients for getting Issues and PullRequests - Update Fody * required * . * .
1 parent 04647b3 commit d86d711

File tree

13 files changed

+792
-719
lines changed

13 files changed

+792
-719
lines changed

examples/RestEase-Client-Generator.md

Lines changed: 634 additions & 40 deletions
Large diffs are not rendered by default.

examples/WireMock.Net.md

Lines changed: 1 addition & 558 deletions
Large diffs are not rendered by default.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using GitHubReleaseNotes.Logic.Models;
5+
using LibGit2Sharp;
6+
7+
namespace GitHubReleaseNotes.Logic.Extensions;
8+
9+
internal static class RepositoryExtensions
10+
{
11+
public static IReadOnlyList<ReleaseInfo> GetOrderedReleaseInfos(this Repository repo, string version)
12+
{
13+
var orderedReleaseInfos = repo.Tags
14+
15+
// Convert Tag into ReleaseInfo
16+
.Select(tag => new ReleaseInfo
17+
{
18+
Version = GetVersionAsLong(tag.FriendlyName) ?? 0,
19+
FriendlyName = tag.FriendlyName,
20+
When = tag.Target is Commit commit ? commit.Committer.When : DateTimeOffset.MinValue
21+
})
22+
23+
// Skip invalid versions
24+
.Where(tag => tag.Version > 0)
25+
26+
// Order by the version
27+
.OrderBy(tag => tag.Version)
28+
.ToList();
29+
30+
// Add the `next` version
31+
orderedReleaseInfos.Add(new ReleaseInfo
32+
{
33+
Version = long.MaxValue,
34+
FriendlyName = version,
35+
When = DateTimeOffset.Now
36+
});
37+
38+
return orderedReleaseInfos;
39+
}
40+
41+
private static long? GetVersionAsLong(string friendlyName)
42+
{
43+
var versionAsString = new string(friendlyName.Where(c => char.IsDigit(c) || c == '.').ToArray());
44+
if (System.Version.TryParse(versionAsString, out var version))
45+
{
46+
return version.Major * 1000000000L + version.Minor * 1000000L + (version.Build > 0 ? version.Build : 0) * 1000L + (version.Revision > 0 ? version.Revision : 0);
47+
}
48+
49+
return null;
50+
}
51+
}

src/GitHubReleaseNotes.Logic/GitHubClientFactory.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ namespace GitHubReleaseNotes.Logic;
55

66
internal static class GitHubClientFactory
77
{
8+
private static readonly TimeSpan RequestTimeout = TimeSpan.FromMinutes(5);
9+
810
private const string AppName = "GitHubReleaseNotes";
911

1012
public static IGitHubClient CreateClient(IConfiguration configuration, string owner)
@@ -14,7 +16,7 @@ public static IGitHubClient CreateClient(IConfiguration configuration, string ow
1416
throw new ArgumentNullException(nameof(configuration));
1517
}
1618

17-
string product = !string.IsNullOrEmpty(owner) ? owner : AppName;
19+
var product = !string.IsNullOrEmpty(owner) ? owner : AppName;
1820
var client = new GitHubClient(new ProductHeaderValue(product));
1921

2022
if (!string.IsNullOrEmpty(configuration.Token))
@@ -26,6 +28,8 @@ public static IGitHubClient CreateClient(IConfiguration configuration, string ow
2628
client.Credentials = new Credentials(configuration.Login, configuration.Password);
2729
}
2830

31+
client.SetRequestTimeout(RequestTimeout);
32+
2933
return client;
3034
}
3135
}
Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,31 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

3-
<PropertyGroup>
4-
<TargetFrameworks>net48;netstandard2.0</TargetFrameworks>
5-
<ProjectGuid>{B6269AAC-170A-43D0-8B9B-579DED3D9A99}</ProjectGuid>
6-
<PackageIcon>icon.png</PackageIcon>
7-
</PropertyGroup>
3+
<PropertyGroup>
4+
<TargetFrameworks>net48;netstandard2.0</TargetFrameworks>
5+
<ProjectGuid>{B6269AAC-170A-43D0-8B9B-579DED3D9A99}</ProjectGuid>
6+
<PackageIcon>icon.png</PackageIcon>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
89

9-
<ItemGroup>
10-
<EmbeddedResource Include="Template.txt" />
11-
</ItemGroup>
10+
<ItemGroup>
11+
<EmbeddedResource Include="Template.txt" />
12+
</ItemGroup>
1213

13-
<ItemGroup>
14-
<PackageReference Include="LibGit2Sharp" Version="0.26.2" />
15-
<PackageReference Include="Octokit" Version="0.48.0" />
16-
<PackageReference Include="Handlebars.Net" Version="2.1.4" />
17-
</ItemGroup>
14+
<ItemGroup>
15+
<PackageReference Include="LibGit2Sharp" Version="0.26.2" />
16+
<PackageReference Include="Octokit" Version="0.48.0" />
17+
<PackageReference Include="Handlebars.Net" Version="2.1.4" />
18+
<PackageReference Include="IsExternalInit" Version="1.0.3">
19+
<PrivateAssets>all</PrivateAssets>
20+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
21+
</PackageReference>
22+
<PackageReference Include="Required" Version="1.0.0">
23+
<PrivateAssets>all</PrivateAssets>
24+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
25+
</PackageReference>
26+
</ItemGroup>
1827

19-
<ItemGroup>
20-
<None Include="../../icon.png" Pack="true" PackagePath="" />
21-
</ItemGroup>
28+
<ItemGroup>
29+
<None Include="../../icon.png" Pack="true" PackagePath="" />
30+
</ItemGroup>
2231
</Project>

src/GitHubReleaseNotes.Logic/Models/IssueInfo.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,17 @@
22

33
internal class IssueInfo
44
{
5-
public int Number { get; set; }
5+
public required int Number { get; set; }
66

7-
public bool IsPulRequest { get; set; }
7+
public required bool IsPulRequest { get; set; }
88

9-
public string IssueUrl { get; set; }
9+
public required string IssueUrl { get; set; }
1010

11-
public string Title { get; set; }
11+
public required string Title { get; set; }
1212

13-
public string User { get; set; }
13+
public required string User { get; set; }
1414

15-
public string UserUrl { get; set; }
15+
public required string UserUrl { get; set; }
1616

17-
public string[] Labels { get; set; }
17+
public required string[] Labels { get; set; }
1818
}

src/GitHubReleaseNotes.Logic/Models/IssuesAndPullRequestsModel.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace GitHubReleaseNotes.Logic.Models;
55

66
internal class IssuesAndPullRequestsModel
77
{
8-
public ICollection<Issue> Issues { get; set; }
8+
public required ICollection<Issue> Issues { get; set; }
99

10-
public ICollection<PullRequest> PullRequests { get; set; }
10+
public required ICollection<PullRequest> PullRequests { get; set; }
1111
}

src/GitHubReleaseNotes.Logic/Models/ReleaseInfo.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ namespace GitHubReleaseNotes.Logic.Models;
55

66
internal class ReleaseInfo
77
{
8-
public long Version { get; set; }
8+
public required long Version { get; set; }
99

10-
public string FriendlyName { get; set; }
10+
public required string FriendlyName { get; set; }
1111

12-
public DateTimeOffset When { get; set; }
12+
public required DateTimeOffset When { get; set; }
1313

14-
public List<IssueInfo> IssueInfos { get; set; }
14+
public List<IssueInfo> IssueInfos { get; set; } = null!;
1515
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
namespace GitHubReleaseNotes.Logic.Models;
2+
3+
internal record RepositorySettings(string Owner, string Name, string HeadBranch);

src/GitHubReleaseNotes.Logic/RepositoryHelper.cs

Lines changed: 35 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
using System.Linq;
55
using System.Text.RegularExpressions;
66
using System.Threading.Tasks;
7+
using GitHubReleaseNotes.Logic.Extensions;
78
using GitHubReleaseNotes.Logic.Models;
8-
using LibGit2Sharp;
99
using Octokit;
1010

1111
namespace GitHubReleaseNotes.Logic;
@@ -26,29 +26,31 @@ public RepositoryHelper(IConfiguration configuration)
2626
internal async Task<IEnumerable<ReleaseInfo>> GetReleaseInfoAsync()
2727
{
2828
var repository = new LibGit2Sharp.Repository(_configuration.RepositoryPath);
29-
string origin = repository.Network.Remotes.First(r => r.Name == "origin").Url;
30-
string url = !origin.EndsWith(".git") ? $"{origin}.git" : origin;
29+
var originUrl = repository.Network.Remotes.First(r => r.Name == "origin").Url;
30+
var gitUrl = !originUrl.EndsWith(".git") ? $"{originUrl}.git" : originUrl;
31+
var headBranchName = repository.Head.FriendlyName;
3132

3233
Console.WriteLine($"Analyzing Git Repository at '{new FileInfo(_configuration.RepositoryPath).FullName}'");
33-
var orderedReleaseInfos = GetOrderedReleaseInfos(repository);
34+
var orderedReleaseInfos = repository.GetOrderedReleaseInfos(_configuration.Version);
3435

35-
Console.WriteLine($"Getting Issues and Pull Requests from '{url}'");
36-
var result = await GetAllIssuesAndPullRequestsAsync(url).ConfigureAwait(false);
36+
Console.WriteLine($"Getting Issues and Pull Requests from '{gitUrl}'");
37+
var result = await GetAllIssuesAndPullRequestsAsync(gitUrl, headBranchName).ConfigureAwait(false);
3738

3839
bool IssueTimeIsLessThenReleaseTime(DateTimeOffset releaseTime, DateTimeOffset? issueClosedTime)
3940
=> issueClosedTime < releaseTime.AddSeconds(DeltaSeconds);
4041

41-
bool IssueTimeIsGreaterThenPreviousReleaseTime(int idx, DateTimeOffset? issueClosedTime) =>
42-
idx <= 0 || issueClosedTime > orderedReleaseInfos[idx - 1].When.AddSeconds(DeltaSeconds);
42+
bool IssueTimeIsGreaterThenPreviousReleaseTime(IReadOnlyList<ReleaseInfo> releaseInfos, int idx, DateTimeOffset? issueClosedTime) =>
43+
idx <= 0 || issueClosedTime > releaseInfos[idx - 1].When.AddSeconds(DeltaSeconds);
4344

44-
bool IssueLinkedToRelease(int idx, ReleaseInfo releaseInfo, DateTimeOffset? issueClosedAtTime) =>
45-
IssueTimeIsLessThenReleaseTime(releaseInfo.When, issueClosedAtTime) && IssueTimeIsGreaterThenPreviousReleaseTime(idx, issueClosedAtTime);
45+
bool IssueLinkedToRelease(IReadOnlyList<ReleaseInfo> releaseInfos, int idx, ReleaseInfo releaseInfo, DateTimeOffset? issueClosedAtTime) =>
46+
IssueTimeIsLessThenReleaseTime(releaseInfo.When, issueClosedAtTime) && IssueTimeIsGreaterThenPreviousReleaseTime(releaseInfos, idx, issueClosedAtTime);
4647

4748
// Loop all orderedReleaseInfos and add the correct Pull Requests and Issues
4849
foreach (var x in orderedReleaseInfos.Select((releaseInfo, index) => new { index, releaseInfo }))
4950
{
5051
// Process only Issues
51-
var issuesForThisTag = result.Issues.Where(issue => issue.PullRequest == null && IssueLinkedToRelease(x.index, x.releaseInfo, issue.ClosedAt));
52+
var releaseInfos = orderedReleaseInfos; // Fix: "Captured variable is modified in outer scope"
53+
var issuesForThisTag = result.Issues.Where(issue => issue.PullRequest == null && IssueLinkedToRelease(releaseInfos, x.index, x.releaseInfo, issue.ClosedAt));
5254
var issueInfos = issuesForThisTag.Select(issue => new IssueInfo
5355
{
5456
Number = issue.Number,
@@ -61,7 +63,7 @@ bool IssueLinkedToRelease(int idx, ReleaseInfo releaseInfo, DateTimeOffset? issu
6163
});
6264

6365
// Process PullRequests
64-
var pullsForThisTag = result.PullRequests.Where(pullRequest => IssueLinkedToRelease(x.index, x.releaseInfo, pullRequest.ClosedAt));
66+
var pullsForThisTag = result.PullRequests.Where(pullRequest => IssueLinkedToRelease(releaseInfos, x.index, x.releaseInfo, pullRequest.ClosedAt));
6567
var pullInfos = pullsForThisTag.Select(pull => new IssueInfo
6668
{
6769
Number = pull.Number,
@@ -94,50 +96,18 @@ bool ExcludeIssue(string[] labels) => _configuration.ExcludeLabels != null &&
9496
return orderedReleaseInfos.OrderByDescending(r => r.Version);
9597
}
9698

97-
private List<ReleaseInfo> GetOrderedReleaseInfos(IRepository repo)
99+
private async Task<IssuesAndPullRequestsModel> GetAllIssuesAndPullRequestsAsync(string gitUrl, string headBranchName)
98100
{
99-
var orderedReleaseInfos = repo.Tags
100-
101-
// Convert Tag into ReleaseInfo
102-
.Select(tag => new ReleaseInfo
103-
{
104-
Version = GetVersionAsLong(tag.FriendlyName) ?? 0,
105-
FriendlyName = tag.FriendlyName,
106-
When = tag.Target is LibGit2Sharp.Commit commit ? commit.Committer.When : DateTimeOffset.MinValue
107-
})
108-
109-
// Skip invalid versions
110-
.Where(tag => tag.Version > 0)
111-
112-
// Order by the version
113-
.OrderBy(tag => tag.Version)
114-
.ToList();
115-
116-
// Add the `next` version
117-
orderedReleaseInfos.Add(new ReleaseInfo
118-
{
119-
Version = long.MaxValue,
120-
FriendlyName = _configuration.Version,
121-
When = DateTimeOffset.Now
122-
});
123-
124-
return orderedReleaseInfos;
125-
}
126-
127-
private async Task<IssuesAndPullRequestsModel> GetAllIssuesAndPullRequestsAsync(string url)
128-
{
129-
GetOwnerAndProject(url, out var owner, out var project);
130-
131-
var client = GitHubClientFactory.CreateClient(_configuration, owner);
101+
GetRepositorySettingsOrThrowException(gitUrl, headBranchName, out var repositorySettings);
132102

133103
//var miscellaneousRateLimit = await client.Miscellaneous.GetRateLimits();
134104
//if (miscellaneousRateLimit.Resources.Core.Remaining < 21)
135105
//{
136106
// throw new Exception($"You have only {miscellaneousRateLimit.Resources.Core.Remaining} Core Requests remaining.");
137107
//}
138108

139-
var issuesTask = GetIssuesForRepositoryAsync(client, owner, project);
140-
var pullRequestsTask = GetMergedPullRequestsForRepositoryAsync(client, owner, project);
109+
var issuesTask = GetIssuesForRepositoryAsync(repositorySettings);
110+
var pullRequestsTask = GetMergedPullRequestsForRepositoryAsync(repositorySettings);
141111

142112
await Task.WhenAll(issuesTask, pullRequestsTask).ConfigureAwait(false);
143113

@@ -148,8 +118,10 @@ private async Task<IssuesAndPullRequestsModel> GetAllIssuesAndPullRequestsAsync(
148118
};
149119
}
150120

151-
private static async Task<ICollection<Issue>> GetIssuesForRepositoryAsync(IGitHubClient client, string owner, string name)
121+
private async Task<ICollection<Issue>> GetIssuesForRepositoryAsync(RepositorySettings repositorySettings)
152122
{
123+
var client = GitHubClientFactory.CreateClient(_configuration, repositorySettings.Owner);
124+
153125
// Do a request to GitHub using Octokit.GitHubClient to get all Closed Issues (this does also include Closed and Merged Pull Requests)
154126
var closedIssuesRequest = new RepositoryIssueRequest
155127
{
@@ -158,45 +130,42 @@ private static async Task<ICollection<Issue>> GetIssuesForRepositoryAsync(IGitHu
158130
};
159131

160132
// Return all Closed issues
161-
return (await client.Issue.GetAllForRepository(owner, name, closedIssuesRequest).ConfigureAwait(false)).OrderBy(i => i.Id).ToList().AsReadOnly();
133+
return (await client.Issue.GetAllForRepository(repositorySettings.Owner, repositorySettings.Name, closedIssuesRequest).ConfigureAwait(false))
134+
.OrderBy(i => i.Id)
135+
.ToList()
136+
.AsReadOnly();
162137
}
163138

164-
private static async Task<ICollection<PullRequest>> GetMergedPullRequestsForRepositoryAsync(IGitHubClient client, string owner, string name)
139+
private async Task<ICollection<PullRequest>> GetMergedPullRequestsForRepositoryAsync(RepositorySettings repositorySettings)
165140
{
141+
var client = GitHubClientFactory.CreateClient(_configuration, repositorySettings.Owner);
142+
166143
// Do a request to GitHub using Octokit.GitHubClient to get all Closed Pull Requests
167144
var closedPullRequestsRequest = new PullRequestRequest
168145
{
169146
//SortDirection = SortDirection.Ascending,
170-
State = ItemStateFilter.Closed
147+
State = ItemStateFilter.Closed,
148+
Base = repositorySettings.HeadBranch
171149
};
172150

173-
// Return only Closes and Merged PullRequests
174-
return (await client.PullRequest.GetAllForRepository(owner, name, closedPullRequestsRequest).ConfigureAwait(false))
151+
// Return only Closed and Merged PullRequests
152+
return (await client.PullRequest.GetAllForRepository(repositorySettings.Owner, repositorySettings.Name, closedPullRequestsRequest).ConfigureAwait(false))
175153
.Where(pull => pull.Merged)
176154
.OrderBy(pull => pull.Id)
177155
.ToList()
178156
.AsReadOnly();
179157
}
180158

181-
private static void GetOwnerAndProject(string url, out string owner, out string project)
159+
private static void GetRepositorySettingsOrThrowException(string url, string headBranchName, out RepositorySettings repositorySettings)
182160
{
183161
var groups = OwnerAndProjectRegex.Match(url).Groups;
184162

185-
if (!TryGetValue(groups, "ownerHttps", "ownerSSH", out owner) || !TryGetValue(groups, "projectHttps", "projectSSH", out project))
163+
if (!TryGetValue(groups, "ownerHttps", "ownerSSH", out var owner) || !TryGetValue(groups, "projectHttps", "projectSSH", out var project))
186164
{
187165
throw new UriFormatException($"The url '{url}' is not a valid GitHub url, the Owner and or Project are not present.");
188166
}
189-
}
190-
191-
private static long? GetVersionAsLong(string friendlyName)
192-
{
193-
var versionAsString = new string(friendlyName.Where(c => char.IsDigit(c) || c == '.').ToArray());
194-
if (System.Version.TryParse(versionAsString, out var version))
195-
{
196-
return version.Major * 1000000000L + version.Minor * 1000000L + (version.Build > 0 ? version.Build : 0) * 1000L + (version.Revision > 0 ? version.Revision : 0);
197-
}
198167

199-
return null;
168+
repositorySettings = new RepositorySettings(owner, project, headBranchName);
200169
}
201170

202171
private static bool TryGetValue(GroupCollection groups, string groupName1, string groupName2, out string value)

0 commit comments

Comments
 (0)