24 Console.WriteLine(
"Missing version argument!");
28 var versionString = args[0];
29 if (!Version.TryParse(versionString, out var version))
31 Console.WriteLine(
"Invalid version: " + versionString);
35 var doNotCloseMilestone = args.Length >= 2 && args[1].ToUpperInvariant() ==
"--NO-CLOSE";
37 const string ReleaseNotesEnvVar =
"TGS_RELEASE_NOTES_TOKEN";
38 var githubToken = Environment.GetEnvironmentVariable(ReleaseNotesEnvVar);
39 if (String.IsNullOrWhiteSpace(githubToken))
41 Console.WriteLine(
"Missing " + ReleaseNotesEnvVar +
" environment variable!");
47 var client =
new GitHubClient(
new ProductHeaderValue(
"tgs_release_notes"));
48 client.Credentials =
new Credentials(githubToken);
50 const string RepoOwner =
"tgstation";
51 const string RepoName =
"tgstation-server";
53 var releasesTask = client.Repository.Release.GetAll(RepoOwner, RepoName);
55 Console.WriteLine(
"Getting pull requests in milestone " + versionString +
"...");
56 var milestonePRs = await client.Search.SearchIssues(
new SearchIssuesRequest
58 Milestone = versionString,
59 Type = IssueTypeQualifier.PullRequest,
60 Repos = { { RepoOwner, RepoName } }
61 }).ConfigureAwait(
false);
63 if (milestonePRs.IncompleteResults)
65 Console.WriteLine(
"Incomplete results for milestone PRs query!");
68 Console.WriteLine(milestonePRs.Items.Count +
" total pull requests");
70 Task<Milestone> milestoneTask = null;
71 var milestoneTaskLock =
new object();
72 var releaseDictionary =
new Dictionary<int, List<string>>();
73 var authorizedUsers =
new Dictionary<long, Task<bool>>();
75 async Task GetReleaseNotesFromPR(Issue pullRequest)
78 var fullPR = await client.Repository.PullRequest.Get(RepoOwner, RepoName, pullRequest.Number).ConfigureAwait(
false);
80 async Task<Milestone> GetMilestone()
82 if (fullPR.Milestone == null)
84 return await client.Issue.Milestone.Get(RepoOwner, RepoName, fullPR.Milestone.Number);
87 lock (milestoneTaskLock)
88 if (milestoneTask == null)
89 milestoneTask = GetMilestone();
94 async Task BuildNotesFromComment(
string comment, User user)
96 var commentSplits = comment.Split(
'\n');
97 var notesOpen =
false;
98 var notesClosed =
false;
99 var notes =
new List<string>();
100 foreach (var line
in commentSplits)
102 var trimmedLine = line.Trim();
105 notesOpen = trimmedLine.StartsWith(
":cl:", StringComparison.Ordinal);
109 if (trimmedLine.StartsWith(
"/:cl:", StringComparison.Ordinal))
115 if (trimmedLine.Length == 0)
117 notes.Add(trimmedLine);
119 if (!notesClosed || notes.Count == 0)
123 TaskCompletionSource<bool> ourTcs = null;
124 lock (authorizedUsers)
126 if (!authorizedUsers.TryGetValue(user.Id, out authTask))
128 ourTcs =
new TaskCompletionSource<bool>();
129 authTask = ourTcs.Task;
130 authorizedUsers.Add(user.Id, authTask);
138 var perm = await client.Repository.Collaborator.ReviewPermission(RepoOwner, RepoName, user.Login).ConfigureAwait(
false);
139 ourTcs.SetResult(perm.Permission == PermissionLevel.Write || perm.Permission == PermissionLevel.Admin);
143 ourTcs.SetResult(
false);
147 var authorized = await authTask.ConfigureAwait(
false);
151 lock (releaseDictionary)
153 foreach (var I
in notes)
154 Console.WriteLine(
"#" + fullPR.Number +
" - " + I +
" (@" + user.Login +
")");
155 if (releaseDictionary.TryGetValue(fullPR.Number, out var currentValues))
156 currentValues.AddRange(notes);
158 releaseDictionary.Add(fullPR.Number, notes);
162 var comments = await client.Issue.Comment.GetAllForIssue(RepoOwner, RepoName, fullPR.Number).ConfigureAwait(
false);
163 await Task.WhenAll(BuildNotesFromComment(fullPR.Body, fullPR.User), Task.WhenAll(comments.Select(x => BuildNotesFromComment(x.Body, x.User)))).ConfigureAwait(
false);
166 var tasks =
new List<Task>();
167 foreach (var I
in milestonePRs.Items)
168 tasks.Add(GetReleaseNotesFromPR(I));
170 var releases = await releasesTask.ConfigureAwait(
false);
172 var releasingSuite = version.Major;
174 Version highestReleaseVersion = null;
175 Release highestRelease = null;
176 foreach (var I
in releases)
178 if (!Version.TryParse(I.TagName.Replace(
"tgstation-server-v", String.Empty), out var currentReleaseVersion))
180 Console.WriteLine(
"WARNING: Unable to determine version of release " + I.HtmlUrl);
184 if (currentReleaseVersion.Major == releasingSuite && (highestReleaseVersion == null || currentReleaseVersion > highestReleaseVersion))
186 highestReleaseVersion = currentReleaseVersion;
191 if (highestReleaseVersion == null)
193 Console.WriteLine(
"Unable to determine highest release version for suite " + releasingSuite +
"!");
197 var oldNotes = highestRelease.Body;
199 var splits =
new List<string>(oldNotes.Split(
'\n'));
202 string keepThisRelease;
203 if (version.Revision == 0)
204 if (version.Build == 0)
205 keepThisRelease =
"# ";
207 keepThisRelease =
"## ";
209 keepThisRelease =
"### ";
211 for (; !splits[0].StartsWith(keepThisRelease, StringComparison.Ordinal); splits.RemoveAt(0))
212 if (splits.Count == 1)
214 Console.WriteLine(
"Error formatting release notes: Can't detemine notes start!");
218 oldNotes = String.Join(
'\n', splits);
221 switch (releasingSuite)
224 prefix =
"The /tg/station server suite";
227 prefix =
"See https://tgstation.github.io/tgstation-server for installation instructions";
231 var newNotes =
new StringBuilder(prefix);
232 newNotes.Append(Environment.NewLine);
233 newNotes.Append(Environment.NewLine);
234 if (version.Revision == 0)
235 if (version.Build == 0)
237 newNotes.Append(
"# [Version ");
238 newNotes.Append(version.Minor);
242 newNotes.Append(
"## [Changelog for ");
243 newNotes.Append(version.Build);
244 newNotes.Append(
".x");
248 newNotes.Append(
"### [Patch ");
249 newNotes.Append(version.Revision);
251 newNotes.Append(
"](");
252 var milestone = await milestoneTask.ConfigureAwait(
false);
253 if (milestone == null)
255 Console.WriteLine(
"Unable to detemine milestone!");
258 newNotes.Append(milestone.HtmlUrl);
259 newNotes.Append(
"?closed=1)");
260 newNotes.Append(Environment.NewLine);
262 await Task.WhenAll(tasks).ConfigureAwait(
false);
264 if (releaseDictionary.Count == 0)
266 Console.WriteLine(
"No release notes for this milestone!");
270 foreach (var I
in releaseDictionary)
271 foreach (var note
in I.Value)
273 newNotes.Append(Environment.NewLine);
274 newNotes.Append(
"- ");
275 newNotes.Append(note);
276 newNotes.Append(
" (#");
277 newNotes.Append(I.Key);
278 newNotes.Append(
')');
281 newNotes.Append(Environment.NewLine);
282 newNotes.Append(Environment.NewLine);
283 newNotes.Append(oldNotes);
285 Console.WriteLine(
"Writing out new release notes...");
286 var releaseNotes = newNotes.ToString();
287 await File.WriteAllTextAsync(
"release_notes.md", releaseNotes).ConfigureAwait(
false);
289 if (doNotCloseMilestone)
290 Console.WriteLine(
"Not closing milestone due to parameter!");
293 Console.WriteLine(
"Closing milestone...");
294 await client.Issue.Milestone.Update(RepoOwner, RepoName, milestone.Number,
new MilestoneUpdate
296 State = ItemState.Closed
297 }).ConfigureAwait(
false);
304 Console.WriteLine(e);