Skip to content

Commit c1c1751

Browse files
authored
Optimize release script (#1489)
Reduced run time from 1 minutes+ to 5 seconds when `--unreleased_only` is enabled.
1 parent aab45bd commit c1c1751

File tree

1 file changed

+44
-21
lines changed

1 file changed

+44
-21
lines changed

scripts/release_notes.py

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@
1919
2020
1. Create a draft release notes / changelog, by running:
2121
22-
$ python3 scripts/release_notes.py --token=<token> --output_unreleased \
23-
--output_without_labels
22+
$ python3 scripts/release_notes.py --token=<token> --unreleased_only
2423
2524
2. Adjust each PR, if necessary, after reading the draft:
2625
@@ -46,7 +45,7 @@
4645

4746
from collections import defaultdict
4847
import urllib
49-
from urllib.request import Request, urlopen, HTTPError
48+
from urllib.request import Request, urlopen
5049
from enum import Enum
5150
import json
5251
import re
@@ -94,6 +93,10 @@ def __init__(self):
9493
self.releases = []
9594
self.merged_prs = []
9695
self.changelog_by_release = defaultdict(ReleaseNotes)
96+
# When True, only gather and output the Unreleased section and
97+
# stop querying older PR pages as soon as we encounter the first
98+
# PR that belongs to a released tag.
99+
self.unreleased_only = False
97100

98101
# Make a Github API call
99102
def github_api(self, url):
@@ -126,26 +129,35 @@ def _get_pr_label_level(self, labels):
126129
elif label['name'] == "release notes: major":
127130
_label_level = LabelLevel.MAJOR_FEATURE
128131
elif label['name'] == "release notes: breaking":
129-
_label_level = LabelLevel.BREAKING
132+
_label_level = LabelLevel.BREAKING_CHANGE
130133
if _label_level > label_level: # retain the highest level
131134
label_level = _label_level
132135
return label_level
133136

134137
# Retrieve the list of all the releases
135138
def get_releases(self):
136-
# Might get into trouble if we ever have more than 30 releases
137-
github_releases, _ = self.github_api('/releases')
138-
for release in github_releases:
139-
ref_data, _ = self.github_api(
140-
'/git/ref/tags/' + release['tag_name'])
141-
tag_data, _ = self.github_api(
142-
'/git/commits/' + ref_data['object']['sha'])
139+
if self.unreleased_only:
140+
# Optimization: only need the latest release to detect boundary
141+
latest_release, _ = self.github_api('/releases/latest')
142+
ref_data, _ = self.github_api('/git/ref/tags/' + latest_release['tag_name'])
143+
tag_data, _ = self.github_api('/git/commits/' + ref_data['object']['sha'])
143144
self.releases.append({
144-
'release': release['tag_name'],
145+
'release': latest_release['tag_name'],
145146
'date': tag_data['author']['date'],
146147
'sha': ref_data['object']['sha'][0:7]
147148
})
148-
self.releases.sort(key=lambda val:val['date'])
149+
else:
150+
# Might get into trouble if we ever have more than 30 releases
151+
github_releases, _ = self.github_api('/releases')
152+
for release in github_releases:
153+
ref_data, _ = self.github_api('/git/ref/tags/' + release['tag_name'])
154+
tag_data, _ = self.github_api('/git/commits/' + ref_data['object']['sha'])
155+
self.releases.append({
156+
'release': release['tag_name'],
157+
'date': tag_data['author']['date'],
158+
'sha': ref_data['object']['sha'][0:7]
159+
})
160+
self.releases.sort(key=lambda val:val['date'])
149161

150162
# Retrieve the list of all merged PRs
151163
def get_merged_prs(self, num_pages):
@@ -174,6 +186,13 @@ def get_merged_prs(self, num_pages):
174186
'label_level': label_level,
175187
})
176188

189+
# Optimization: if only unreleased requested and we hit
190+
# a PR that's already part of a released tag, we can
191+
# stop. The API returns PRs in reverse chronological
192+
# order, so older ones will also be released.
193+
if self.unreleased_only and release != UNRELEASED:
194+
return
195+
177196
num_pages -= 1
178197
if num_pages == 0:
179198
break
@@ -215,11 +234,13 @@ def format_release_notes(self):
215234
release_notes.without_labels.append(final_formatted_line)
216235

217236
# Print the final result in the form of CHANGELOG.md
218-
def print_changelog(self, output_without_labels, output_unreleased):
237+
def print_changelog(self, output_without_labels, output_unreleased_only):
219238
print("[//]: # (GENERATED FILE -- DO NOT EDIT!)")
220239
print("[//]: # (See scripts/release_notes.py for more details.)")
221240
for release, release_notes in self.changelog_by_release.items():
222-
if release == UNRELEASED and output_unreleased == False:
241+
# Always include Unreleased unless we're filtering to only
242+
# unreleased (handled by the check below).
243+
if output_unreleased_only and release != UNRELEASED:
223244
continue
224245
print_other_changes_heading = False
225246
print("")
@@ -268,26 +289,28 @@ def build_args_parser():
268289
default=False,
269290
action='store_true',
270291
help='Whether to output PRs without labels')
271-
parser.add_argument('--output_unreleased',
292+
parser.add_argument('--unreleased_only',
272293
default=False,
273294
action='store_true',
274-
help='Whether to output unreleased')
295+
help='Only output the Unreleased section (Including PRs without labels)')
275296
return parser
276297

277298
def main():
278299
parser = build_args_parser()
279300
args = parser.parse_args()
280301
token, num_pages = args.token, args.num_pages
281-
output_unreleased = args.output_unreleased
282-
output_without_labels = args.output_without_labels
302+
unreleased_only = args.unreleased_only
303+
# If --unreleased_only is set, we implicitly enable without-labels output.
304+
output_without_labels = args.output_without_labels or unreleased_only
283305
if token == "":
284306
print("Error: Github API token is required --token=<token>")
285307
return
286308

287309
worker = ProcessChangelog()
288310
worker.token = token
311+
worker.unreleased_only = unreleased_only
289312

290-
# Retrieve the list of all the releases
313+
# Retrieve the list of all the releases (optimized when unreleased_only)
291314
worker.get_releases()
292315

293316
# Retrieve the list of all merged PRs
@@ -298,7 +321,7 @@ def main():
298321
worker.format_release_notes()
299322

300323
# Print the final result in the form of CHANGELOG.md
301-
worker.print_changelog(output_without_labels, output_unreleased)
324+
worker.print_changelog(output_without_labels, unreleased_only)
302325

303326
if __name__ == "__main__":
304327
main()

0 commit comments

Comments
 (0)