19
19
20
20
1. Create a draft release notes / changelog, by running:
21
21
22
- $ python3 scripts/release_notes.py --token=<token> --output_unreleased \
23
- --output_without_labels
22
+ $ python3 scripts/release_notes.py --token=<token> --unreleased_only
24
23
25
24
2. Adjust each PR, if necessary, after reading the draft:
26
25
46
45
47
46
from collections import defaultdict
48
47
import urllib
49
- from urllib .request import Request , urlopen , HTTPError
48
+ from urllib .request import Request , urlopen
50
49
from enum import Enum
51
50
import json
52
51
import re
@@ -94,6 +93,10 @@ def __init__(self):
94
93
self .releases = []
95
94
self .merged_prs = []
96
95
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
97
100
98
101
# Make a Github API call
99
102
def github_api (self , url ):
@@ -126,26 +129,35 @@ def _get_pr_label_level(self, labels):
126
129
elif label ['name' ] == "release notes: major" :
127
130
_label_level = LabelLevel .MAJOR_FEATURE
128
131
elif label ['name' ] == "release notes: breaking" :
129
- _label_level = LabelLevel .BREAKING
132
+ _label_level = LabelLevel .BREAKING_CHANGE
130
133
if _label_level > label_level : # retain the highest level
131
134
label_level = _label_level
132
135
return label_level
133
136
134
137
# Retrieve the list of all the releases
135
138
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' ])
143
144
self .releases .append ({
144
- 'release' : release ['tag_name' ],
145
+ 'release' : latest_release ['tag_name' ],
145
146
'date' : tag_data ['author' ]['date' ],
146
147
'sha' : ref_data ['object' ]['sha' ][0 :7 ]
147
148
})
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' ])
149
161
150
162
# Retrieve the list of all merged PRs
151
163
def get_merged_prs (self , num_pages ):
@@ -174,6 +186,13 @@ def get_merged_prs(self, num_pages):
174
186
'label_level' : label_level ,
175
187
})
176
188
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
+
177
196
num_pages -= 1
178
197
if num_pages == 0 :
179
198
break
@@ -215,11 +234,13 @@ def format_release_notes(self):
215
234
release_notes .without_labels .append (final_formatted_line )
216
235
217
236
# 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 ):
219
238
print ("[//]: # (GENERATED FILE -- DO NOT EDIT!)" )
220
239
print ("[//]: # (See scripts/release_notes.py for more details.)" )
221
240
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 :
223
244
continue
224
245
print_other_changes_heading = False
225
246
print ("" )
@@ -268,26 +289,28 @@ def build_args_parser():
268
289
default = False ,
269
290
action = 'store_true' ,
270
291
help = 'Whether to output PRs without labels' )
271
- parser .add_argument ('--output_unreleased ' ,
292
+ parser .add_argument ('--unreleased_only ' ,
272
293
default = False ,
273
294
action = 'store_true' ,
274
- help = 'Whether to output unreleased ' )
295
+ help = 'Only output the Unreleased section (Including PRs without labels) ' )
275
296
return parser
276
297
277
298
def main ():
278
299
parser = build_args_parser ()
279
300
args = parser .parse_args ()
280
301
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
283
305
if token == "" :
284
306
print ("Error: Github API token is required --token=<token>" )
285
307
return
286
308
287
309
worker = ProcessChangelog ()
288
310
worker .token = token
311
+ worker .unreleased_only = unreleased_only
289
312
290
- # Retrieve the list of all the releases
313
+ # Retrieve the list of all the releases (optimized when unreleased_only)
291
314
worker .get_releases ()
292
315
293
316
# Retrieve the list of all merged PRs
@@ -298,7 +321,7 @@ def main():
298
321
worker .format_release_notes ()
299
322
300
323
# 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 )
302
325
303
326
if __name__ == "__main__" :
304
327
main ()
0 commit comments