private string FormatPulllRequestDescription(CachedPullRequest pull, int index)
        {
            // Use "release_notes" comment if exists, otherwise PR title
            var comments = pull.Comments.Where(x => x.Body.ToLower().StartsWith("release_notes:"));

            if (comments.Count() <= 0)
            {
                throw new Exception($"PR #{pull.PullRequest.Number} does not have a release_notes entry!");
            }
            else
            {
                return(comments.ToList()[index].Body.Substring("release_notes:".Length + 1).Trim());
            }
        }
        private async Task <Dictionary <int, CachedPullRequest> > GetMergedPullRequestsBetween2Refs(string owner, string repository, string fromRef, string toRef, int batchSize)
        {
            IEnumerable <GitHubCommit> commits = null;

            // Get commits for the from/to range
            Console.WriteLine($"Get commits from {owner}/{repository} in specified range ({fromRef} - {toRef})");
            var response = await this.GitHubClient.Repository.Commit.Compare(owner, repository, fromRef, toRef);

            commits = response.Commits;

            // First load the Issues (PullRequests) for the date range of our commits
            var from = commits.Min(x => x.Commit.Committer.Date).UtcDateTime;
            var to   = commits.Max(x => x.Commit.Committer.Date).UtcDateTime;

            Console.WriteLine($"Find Pull Requests merged in this date range {from.ToLocalTime()} - {to.ToLocalTime()}");
            var searchRequest = new SearchIssuesRequest
            {
                Merged = new DateRange(from, to),
                Type   = IssueTypeQualifier.PullRequest,
                Repos  = new RepositoryCollection {
                    string.Concat(owner, "/", repository)
                },
                PerPage = 1000
            };
            var searchResults = await this.GitHubClient.Search.SearchIssues(searchRequest);

            Console.WriteLine($"Found {searchResults.Items.Count} Pull Requests merged in this date range");

            // Then load the merge events for each PullRequest in parallel
            Console.WriteLine("Get merge events for each Pull Request");
            var eventsTasks = searchResults.Items.Select(issue =>
            {
                return(Task.Run(async() =>
                {
                    var events = await this.GitHubClient.Issue.Events.GetAllForIssue(owner, repository, issue.Number);
                    return new KeyValuePair <int, IssueEvent>(issue.Number, events.FirstOrDefault(x => x.Event == EventInfoState.Merged));
                }));
            });
            var mergeEvents = await Task.WhenAll(eventsTasks);

            // Some of these PRs were in our time range but not for our actual commit range, so  determine which Pull Requests we actually care about (their merge commit is in our commit list)
            Console.WriteLine("Exclude Pull Requests whose merge commit isn't in our commit range");
            var mergedPullRequests = searchResults.Items.Where(x => mergeEvents.Any(y => x.Number == y.Key && commits.Any(z => z.Sha == y.Value.CommitId)));

            Console.WriteLine($"Found {mergedPullRequests.Count()} merged Pull Requests to load");

            // Now load details about the PullRequests using parallel async tasks
            List <CachedPullRequest> cache = new List <CachedPullRequest>();

            for (int i = 0; i < mergedPullRequests.Count(); i += batchSize)
            {
                var batchPullRequests = mergedPullRequests
                                        .Skip(i)
                                        .Take(batchSize);

                var tasks = batchPullRequests.Select(pull =>
                {
                    return(Task.Run(async() =>
                    {
                        // Load the PR, Issue, Commits and Comments
                        var pullRequestTask = this.GitHubClient.PullRequest.Get(owner, repository, pull.Number);
                        var issueTask = this.GitHubClient.Issue.Get(owner, repository, pull.Number);
                        var pullRequestCommitsTask = this.GitHubClient.PullRequest.Commits(owner, repository, pull.Number);
                        var pullRequestCommentsTask = this.GitHubClient.Issue.Comment.GetAllForIssue(owner, repository, pull.Number);

                        await Task.WhenAll(pullRequestTask, issueTask, pullRequestCommitsTask, pullRequestCommentsTask);

                        // Need to load commits individually to get the author details
                        var pullRequestFullCommitsTask = pullRequestCommitsTask.Result.Select(x => this.GitHubClient.Repository.Commit.Get(owner, repository, x.Sha));

                        var pullRequestFullCommits = await Task.WhenAll(pullRequestFullCommitsTask);

                        // Extract the distinct users who have commits in the PR
                        var contributorUsers = pullRequestFullCommits
                                               .Where(x => x.Author != null && x.Author.Id != 0)
                                               .Select(x => x.Author)
                                               .DistinctBy(x => x.Login);

                        // Gather everything into a CachedPullRequest object
                        var cachedPullRequest = new CachedPullRequest()
                        {
                            PullRequest = pullRequestTask.Result,
                            Issue = issueTask.Result,
                            Commits = pullRequestCommitsTask.Result.ToList(),
                            Comments = pullRequestCommentsTask.Result.ToList(),
                            MergeCommitSha = mergeEvents.FirstOrDefault(x => x.Key == pull.Number).Value.CommitId,
                            Contributors = contributorUsers.ToList(),
                            Labels = issueTask.Result.Labels.Select(x => x.Name)
                        };

                        return cachedPullRequest;
                    }));
                });

                // Collect the results
                cache.AddRange(await Task.WhenAll(tasks));

                Console.WriteLine($"Loaded {cache.Count} Pull Requests");
            }

            return(cache.ToDictionary(x => x.PullRequest.Number));
        }
        private string FormatPulllRequestAdvisory(CachedPullRequest pull, int index)
        {
            var comments = pull.Comments.Where(x => x.Body.ToLower().StartsWith("advisories:"));

            return(comments.ToList()[index].Body.Substring("advisories:".Length + 1).Trim());
        }
        private int GetNumberReleaseNotes(CachedPullRequest pull)
        {
            var comments = pull.Comments.Where(x => x.Body.ToLower().StartsWith("release_notes:"));

            return(Math.Max(1, comments.Count()));
        }
        private int GetNumberAdvisories(CachedPullRequest pull)
        {
            var comments = pull.Comments.Where(x => x.Body.ToLower().StartsWith("advisories:"));

            return(comments.Count());
        }