1919
20201. 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
25242. Adjust each PR, if necessary, after reading the draft:
2625
4645
4746from collections import defaultdict
4847import urllib
49- from urllib .request import Request , urlopen , HTTPError
48+ from urllib .request import Request , urlopen
5049from enum import Enum
5150import json
5251import 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
277298def 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
303326if __name__ == "__main__" :
304327 main ()
0 commit comments