Skip to content

Commit 19f3bf2

Browse files
kikitoaboudreault
authored andcommitted
chore(scripts) modify changelog helper to return a list of authors and their PRs
1 parent 89eedcf commit 19f3bf2

File tree

1 file changed

+100
-177
lines changed

1 file changed

+100
-177
lines changed

scripts/changelog-helper.lua

+100-177
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ local USAGE = [[
1919

2020

2121
local KNOWN_KONGERS = { -- include kong alumni here
22-
hishamhm = true,
2322
p0pr0ck5 = true,
2423
}
2524

@@ -129,7 +128,12 @@ local function get_comparison_commits(api, from_ref, to_ref)
129128
local latest_ancestor_iso8601 = os.date("!%Y-%m-%dT%TZ", latest_ancestor_epoch)
130129

131130
local commits = {}
131+
--local count = 0
132132
for commit in api.iterate_paged(fmt("/repos/kong/kong/commits?since=%s", latest_ancestor_iso8601)) do
133+
--if count >= 10 then
134+
--return commits
135+
--end
136+
--count = count + 1
133137
if datetime_to_epoch(commit.commit.committer.date) > latest_ancestor_epoch then
134138
commits[#commits + 1] = commit
135139
--print("sha: ", commit.sha, ", date: ", commit.commit.committer.date, ", epoch: ", datetime_to_epoch(commit.commit.committer.date))
@@ -171,8 +175,10 @@ local function get_prs_from_comparison_commits(api, commits)
171175

172176
-- optimization: preload all commits for this PR into pr_by_commit_sha to avoid unnecessary calls to /repos/kong/kong/commits/%s/pulls
173177
local pr_commits_res = api.get(fmt("/repos/kong/kong/pulls/%d/commits?per_page=100", pr.number))
174-
for _, pr_commit in ipairs(pr_commits_res) do
175-
pr_by_commit_sha[pr_commit.sha] = pr
178+
if type(pr_commits_res) ~= "table" then
179+
for _, pr_commit in ipairs(pr_commits_res) do
180+
pr_by_commit_sha[pr_commit.sha] = pr
181+
end
176182
end
177183
end
178184
pr.commits = pr.commits or {}
@@ -186,6 +192,29 @@ local function get_prs_from_comparison_commits(api, commits)
186192
end
187193

188194

195+
local function get_prs_from_changelog_hash()
196+
print("\n\nParsing current changelog")
197+
local prs_from_changelog_hash = {}
198+
199+
local changelog_filename = "CHANGELOG.md"
200+
201+
local f = assert(io.open(changelog_filename, "r"))
202+
local line
203+
repeat
204+
line = f:read("*line")
205+
if line then
206+
for pr in line:gmatch('#(%d%d%d?%d?%d?)') do
207+
io.write("#", pr, ", ")
208+
prs_from_changelog_hash[assert(tonumber(pr))] = true
209+
end
210+
end
211+
until not line
212+
f:close()
213+
214+
return prs_from_changelog_hash
215+
end
216+
217+
189218
local function get_non_konger_authors(api, commits)
190219
print("\n\nFinding non-konger authors")
191220
local author_logins_hash = {}
@@ -204,7 +233,7 @@ local function get_non_konger_authors(api, commits)
204233
local _, status = api.get(fmt("/orgs/kong/memberships/%s", login))
205234
if status == 404 then
206235
non_kongers[login] = true
207-
io.stdout:write("")
236+
io.stdout:write("🌎")
208237
else
209238
io.stdout:write("🦍")
210239
end
@@ -217,25 +246,22 @@ end
217246

218247

219248
local function extract_type_and_scope_and_title(str)
220-
local typ, scope, title = string.match(str, "^([^%(]+)%(([^%)]+)%) (.+)$")
249+
local typ, scope, title = string.match(str, "^([^%(]+)%(([^%)]+)%) ?(.+)$")
221250
return typ, scope, title
222251
end
223252

224253

225-
local function get_first_line(str)
226-
return str:match("^([^\n]+)")
227-
end
228-
229-
-- Transforms the list of PRs into a shorter table that is easier to get a report out of
254+
-- Transforms the list of PRs into a shorter table that is organized by author
230255
local function categorize_prs(prs)
231256
print("\n\nCategorizing PRs")
232257
local categorized_prs = {}
233258
local commits, authors_hash
259+
234260
for pr_number,pr in pairs(prs) do
235261
commits = {}
236262
authors_hash = {}
237263
for _,c in ipairs(pr.commits) do
238-
if c.author and c.author.login then
264+
if type(c.author) == "table" and c.author.login then
239265
authors_hash[c.author.login] = true
240266
end
241267
commits[#commits + 1] = c.commit.message
@@ -257,7 +283,7 @@ local function categorize_prs(prs)
257283
end
258284
table.sort(authors)
259285

260-
categorized_prs[pr_number] = {
286+
table.insert(categorized_prs, {
261287
number = pr_number,
262288
title = title,
263289
typ = typ,
@@ -266,198 +292,93 @@ local function categorize_prs(prs)
266292
description = pr.body,
267293
commits = commits,
268294
authors = authors,
269-
}
295+
})
270296
end
271297

272298
return categorized_prs
273299
end
274300

275-
-- to_sentence({}) = ""
276-
-- to_sentence({"a"}) = "a"
277-
-- to_sentence({"a", "b"}) = "a and b"
278-
-- to_sentence({"a", "b", "c" }) = "a, b and c"
279-
local function to_sentence(arr)
280-
local buffer = {}
281-
local len = #arr
282-
for i = 1, len do
283-
buffer[i * 2 - 1] = arr[i]
284-
if i < len - 1 then
285-
buffer[i * 2] = ", "
286-
elseif i == len - 1 then
287-
buffer[i * 2] = " and "
288-
end
289-
end
290-
return table.concat(buffer)
291-
end
292-
293-
local function render_pr_li_thank_you(authors, non_kongers_hash)
294-
local non_kongers_links = {}
295-
for _,login in ipairs(authors) do
296-
if non_kongers_hash[login] then
297-
non_kongers_links[#non_kongers_links + 1] = fmt("[%s](https://github.com/%s)", login, login)
298-
end
299-
end
300-
if #non_kongers_links == 0 then
301-
return "."
302-
end
303-
return fmt("\n Thanks %s for the patch!", to_sentence(non_kongers_links))
304-
end
305-
306-
local function render_pr_li_markdown(pr, non_kongers_hash)
307-
return fmt([[
308-
- %s
309-
[#%d](%s)%s
310-
]], pr.title, pr.number, pr.url, render_pr_li_thank_you(pr.authors, non_kongers_hash))
301+
local function pr_needed_in_changelog(pr)
302+
return pr.typ ~= "tests" and
303+
pr.typ ~= "hotfix" and
304+
pr.typ ~= "docs" and
305+
pr.typ ~= "doc" and
306+
pr.typ ~= "style" and
307+
(pr.typ ~= "chore" or pr.scope == "deps")
311308
end
312309

313310

314-
local function print_report(categorized_prs, non_pr_commits, non_kongers_hash, to_ref)
315-
local pr_numbers = {}
316-
for pr_number in pairs(categorized_prs) do
317-
pr_numbers[#pr_numbers + 1] = pr_number
318-
end
319-
table.sort(pr_numbers)
320-
311+
local function print_report(categorized_prs, non_pr_commits, non_kongers_hash, to_ref, prs_from_changelog_hash)
321312
print("=================================================")
322313

323-
-- Dependencies
324-
local first_dep = true
325-
for _, pr_number in ipairs(pr_numbers) do
326-
local pr = categorized_prs[pr_number]
314+
local prs_by_author = {}
327315

328-
if pr.typ == "chore" and (pr.scope == "deps" or pr.scope == "rockspec") then
329-
if first_dep then
330-
first_dep = false
331-
print("\n\n### Dependencies\n")
332-
end
333-
pr.reported = true
334-
print(render_pr_li_markdown(pr, non_kongers_hash))
316+
for _,pr in pairs(categorized_prs) do
317+
for _,a in ipairs(pr.authors) do
318+
prs_by_author[a] = prs_by_author[a] or {}
319+
table.insert(prs_by_author[a], pr)
335320
end
336321
end
337322

338-
339-
local categories_markdown = [[
340-
##### Core
341-
342-
##### CLI
343-
344-
##### Configuration
345-
346-
##### Admin API
347-
348-
##### PDK
349-
350-
##### Plugins
351-
]]
352-
353-
local feats = {}
354-
local fixes = {}
355-
local unknown = {}
356-
for _, pr_number in ipairs(pr_numbers) do
357-
local pr = categorized_prs[pr_number]
358-
if pr.typ == "feat" then
359-
feats[#feats + 1] = pr
360-
elseif pr.typ == "fix" then
361-
fixes[#fixes + 1] = pr
362-
elseif not pr.reported then
363-
unknown[#unknown + 1] = pr
364-
end
323+
for author, prs in pairs(prs_by_author) do
324+
table.sort(prs, function(pra, prb)
325+
return pra.number < prb.number
326+
end)
365327
end
366328

367-
local sort_by_scope = function(a,b)
368-
if a.scope == b.scope then
369-
return a.number < b.number
370-
end
371-
return a.scope < b.scope
329+
local authors_array = {}
330+
for author in pairs(prs_by_author) do
331+
table.insert(authors_array, author)
372332
end
373-
table.sort(feats, sort_by_scope)
374-
table.sort(fixes, sort_by_scope)
375-
table.sort(unknown, sort_by_scope)
376-
377-
for i, pr in ipairs(feats) do
378-
if i == 1 then
379-
print([[
380-
381-
382-
### Additions
383-
384-
Note: Categorize the additions below into one of these categories (add categories if needed).
385-
Remove this note
386-
387-
]], categories_markdown)
333+
table.sort(authors_array, function(a,b)
334+
return non_kongers_hash[a] or non_kongers_hash[b] or a < b
335+
end)
336+
337+
for _,author in ipairs(authors_array) do
338+
print("\n\n## ", author, non_kongers_hash[author] and " 🌎" or " 🦍", "\n")
339+
340+
local prs = prs_by_author[author]
341+
local in_changelog_prs = {}
342+
local non_changelog_prs = {}
343+
local candidate_changelog_prs = {}
344+
for _,pr in ipairs(prs) do
345+
if(prs_from_changelog_hash[pr.number]) then
346+
table.insert(in_changelog_prs, pr)
347+
elseif pr_needed_in_changelog(pr) then
348+
table.insert(candidate_changelog_prs, pr)
349+
else
350+
table.insert(non_changelog_prs, pr)
351+
end
388352
end
389-
print(render_pr_li_markdown(pr, non_kongers_hash))
390-
end
391353

392-
for i, pr in ipairs(fixes) do
393-
if i == 1 then
394-
print([[
395-
396-
397-
### Fixes
398-
399-
Note: Categorize the fixes below into one of these categories (add categories if needed).
400-
Remove this note
401-
402-
]], categories_markdown)
354+
if #candidate_changelog_prs > 0 then
355+
print("\nProbably need to be in changelog:")
356+
for i,pr in ipairs(candidate_changelog_prs) do
357+
print(fmt(" - [#%d %s/%s %s](%s)", pr.number, pr.typ, pr.scope, pr.title, pr.url))
358+
--print(pr.description)
359+
--for _,c in ipairs(pr.commits) do
360+
--print(fmt(" - %s", c))
361+
--end
362+
end
403363
end
404-
print(render_pr_li_markdown(pr, non_kongers_hash))
405-
end
406-
407-
408-
for i, pr in ipairs(unknown) do
409-
if i == 1 then
410-
print([[
411364

412-
413-
### Unknown PRs
414-
415-
The following PRs could not be identified as either fixes or feats. Please move them to their appropiate place or discard them.
416-
Remove this whole section afterwards.
417-
418-
]])
365+
if #non_changelog_prs > 0 then
366+
print("\nProbably *not* needed on changelog (by type of pr):")
367+
for i,pr in ipairs(non_changelog_prs) do
368+
print(fmt(" - [#%d %s/%s %s](%s)", pr.number, pr.typ, pr.scope, pr.title, pr.url))
369+
end
419370
end
420371

421-
print(fmt([[
422-
- %s
423-
[#%d](%s)%s
424-
Categorization: %s(%s)
425-
Commits:]],
426-
pr.title, pr.number, pr.url, render_pr_li_thank_you(pr.authors, non_kongers_hash), pr.typ, pr.scope))
427-
428-
for _, commit in ipairs(pr.commits) do
429-
print(fmt([[
430-
- %s]], get_first_line(commit)))
372+
if #in_changelog_prs > 0 then
373+
print("\nAlready detected in changelog (by PR number): ")
374+
for i,pr in ipairs(in_changelog_prs) do
375+
print(fmt(" - [#%d %s/%s %s](%s)", pr.number, pr.typ, pr.scope, pr.title, pr.url))
376+
end
431377
end
432-
end
433-
434378

435-
for i,commit in ipairs(non_pr_commits) do
436-
if i == 1 then
437-
print(fmt([[
438379

439-
### Non-PR commits
440380

441-
I could not find the PR for the following commits. They are likely direct pushes against %s.
442-
443-
]], to_ref))
444-
end
445-
446-
local msg = commit.commit.message
447-
local typ, scope = extract_type_and_scope_and_title(msg)
448-
if not typ then
449-
typ, scope = "unknown", "unknown"
450-
end
451-
local author = commit.author and (commit.author.login or commit.author.name) or "unknown"
452-
453-
print(fmt([[
454-
- %s
455-
[%s](%s)
456-
Categorization: %s(%s)
457-
Author: %s
458-
]], get_first_line(msg), commit.sha, commit.html_url, typ, scope, author))
459381
end
460-
461382
end
462383

463384

@@ -477,8 +398,10 @@ local commits = get_comparison_commits(api, from_ref, to_ref)
477398

478399
local prs, non_pr_commits = get_prs_from_comparison_commits(api, commits)
479400

401+
local prs_from_changelog_hash = get_prs_from_changelog_hash()
402+
480403
local categorized_prs = categorize_prs(prs)
481404

482405
local non_kongers_hash = get_non_konger_authors(api, commits)
483406

484-
print_report(categorized_prs, non_pr_commits, non_kongers_hash, to_ref)
407+
print_report(categorized_prs, non_pr_commits, non_kongers_hash, to_ref, prs_from_changelog_hash)

0 commit comments

Comments
 (0)