11use std:: collections:: BTreeMap ;
2+ use std:: collections:: HashMap ;
23use std:: env;
34
45use askama:: Template ;
@@ -8,8 +9,6 @@ use chrono::Duration;
89use reqwest:: header:: HeaderMap ;
910use serde_json as json;
1011
11- type JsonRefArray < ' a > = Vec < & ' a json:: Value > ;
12-
1312const SKIP_LABELS : & [ & str ] = & [
1413 "beta-nominated" ,
1514 "beta-accepted" ,
@@ -18,6 +17,13 @@ const SKIP_LABELS: &[&str] = &[
1817 "rollup" ,
1918] ;
2019
20+ const RELNOTES_LABELS : & [ & str ] = & [
21+ "relnotes" ,
22+ "relnotes-perf" ,
23+ "finished-final-comment-period" ,
24+ "needs-fcp" ,
25+ ] ;
26+
2127#[ derive( Clone , Template ) ]
2228#[ template( path = "relnotes.md" , escape = "none" ) ]
2329struct ReleaseNotes {
@@ -35,6 +41,8 @@ struct ReleaseNotes {
3541 unsorted : String ,
3642 unsorted_relnotes : String ,
3743 version : String ,
44+ internal_changes_relnotes : String ,
45+ internal_changes_unsorted : String ,
3846}
3947
4048fn main ( ) {
@@ -54,8 +62,8 @@ fn main() {
5462 end = end + six_weeks;
5563 }
5664
57- let mut issues = get_issues_by_milestone ( & version, "rust" ) ;
58- issues . sort_by_cached_key ( |issue| issue [ "number" ] . as_u64 ( ) . unwrap ( ) ) ;
65+ let issues = get_issues_by_milestone ( & version, "rust" ) ;
66+ let mut tracking_rust = TrackingIssues :: collect ( & issues ) ;
5967
6068 // Skips `beta-accepted` as those PRs were backported onto the
6169 // previous stable.
@@ -67,30 +75,75 @@ fn main() {
6775 . any ( |o| SKIP_LABELS . contains ( & o[ "name" ] . as_str ( ) . unwrap ( ) ) )
6876 } ) ;
6977
70- let relnotes_tags = & [ " relnotes" , "finished-final-comment-period" , "needs-fcp" ] ;
71-
72- let ( relnotes , rest ) = partition_by_tag ( in_release , relnotes_tags ) ;
78+ let ( relnotes, rest ) = in_release
79+ . into_iter ( )
80+ . partition :: < Vec < _ > , _ > ( |o| has_tags ( o , RELNOTES_LABELS ) ) ;
7381
7482 let (
7583 compat_relnotes,
7684 libraries_relnotes,
7785 language_relnotes,
7886 compiler_relnotes,
87+ internal_changes_relnotes,
7988 unsorted_relnotes,
80- ) = partition_prs ( relnotes) ;
89+ ) = to_sections ( relnotes, & mut tracking_rust ) ;
8190
82- let ( compat_unsorted, libraries_unsorted, language_unsorted, compiler_unsorted, unsorted) =
83- partition_prs ( rest) ;
91+ let (
92+ compat_unsorted,
93+ libraries_unsorted,
94+ language_unsorted,
95+ compiler_unsorted,
96+ internal_changes_unsorted,
97+ unsorted,
98+ ) = to_sections ( rest, & mut tracking_rust) ;
8499
85- let mut cargo_issues = get_issues_by_milestone ( & version, "cargo" ) ;
86- cargo_issues. sort_by_cached_key ( |issue| issue[ "number" ] . as_u64 ( ) . unwrap ( ) ) ;
100+ let cargo_issues = get_issues_by_milestone ( & version, "cargo" ) ;
87101
88102 let ( cargo_relnotes, cargo_unsorted) = {
89- let ( relnotes, rest) = partition_by_tag ( cargo_issues. iter ( ) , relnotes_tags) ;
103+ let ( relnotes, rest) = cargo_issues
104+ . iter ( )
105+ . partition :: < Vec < _ > , _ > ( |o| has_tags ( o, RELNOTES_LABELS ) ) ;
90106
91- ( map_to_line_items ( relnotes) , map_to_line_items ( rest) )
107+ (
108+ relnotes
109+ . iter ( )
110+ . map ( |o| {
111+ format ! (
112+ "- [{title}]({url}/)" ,
113+ title = o[ "title" ] . as_str( ) . unwrap( ) ,
114+ url = o[ "url" ] . as_str( ) . unwrap( ) ,
115+ )
116+ } )
117+ . collect :: < Vec < _ > > ( )
118+ . join ( "\n " ) ,
119+ rest. iter ( )
120+ . map ( |o| {
121+ format ! (
122+ "- [{title}]({url}/)" ,
123+ title = o[ "title" ] . as_str( ) . unwrap( ) ,
124+ url = o[ "url" ] . as_str( ) . unwrap( ) ,
125+ )
126+ } )
127+ . collect :: < Vec < _ > > ( )
128+ . join ( "\n " ) ,
129+ )
92130 } ;
93131
132+ for issue in tracking_rust. issues . values ( ) {
133+ for ( section, ( used, _) ) in issue. sections . iter ( ) {
134+ if * used {
135+ continue ;
136+ }
137+
138+ eprintln ! (
139+ "Did not use {:?} from {} <{}>" ,
140+ section,
141+ issue. raw[ "title" ] . as_str( ) . unwrap( ) ,
142+ issue. raw[ "url" ] . as_str( ) . unwrap( )
143+ ) ;
144+ }
145+ }
146+
94147 let relnotes = ReleaseNotes {
95148 version,
96149 date : ( end + six_weeks) . naive_utc ( ) ,
@@ -104,6 +157,8 @@ fn main() {
104157 compiler_unsorted,
105158 cargo_relnotes,
106159 cargo_unsorted,
160+ internal_changes_relnotes,
161+ internal_changes_unsorted,
107162 unsorted_relnotes,
108163 unsorted,
109164 } ;
@@ -112,11 +167,29 @@ fn main() {
112167}
113168
114169fn get_issues_by_milestone ( version : & str , repo_name : & ' static str ) -> Vec < json:: Value > {
170+ let mut out = get_issues_by_milestone_inner ( version, repo_name, "issues" ) ;
171+ out. extend ( get_issues_by_milestone_inner (
172+ version,
173+ repo_name,
174+ "pullRequests" ,
175+ ) ) ;
176+ out. sort_unstable_by_key ( |v| v[ "number" ] . as_u64 ( ) . unwrap ( ) ) ;
177+ out. dedup_by_key ( |v| v[ "number" ] . as_u64 ( ) . unwrap ( ) ) ;
178+ out
179+ }
180+
181+ fn get_issues_by_milestone_inner (
182+ version : & str ,
183+ repo_name : & ' static str ,
184+ ty : & str ,
185+ ) -> Vec < json:: Value > {
115186 use reqwest:: blocking:: Client ;
116187
117188 let headers = request_header ( ) ;
118189 let mut args = BTreeMap :: new ( ) ;
119- args. insert ( "states" , String :: from ( "[MERGED]" ) ) ;
190+ if ty == "pullRequests" {
191+ args. insert ( "states" , String :: from ( "[MERGED]" ) ) ;
192+ }
120193 args. insert ( "last" , String :: from ( "100" ) ) ;
121194 let mut issues = Vec :: new ( ) ;
122195
@@ -128,11 +201,12 @@ fn get_issues_by_milestone(version: &str, repo_name: &'static str) -> Vec<json::
128201 milestones(query: "{version}", first: 1) {{
129202 totalCount
130203 nodes {{
131- pullRequests ({args}) {{
204+ {ty} ({args}) {{
132205 nodes {{
133206 number
134207 title
135208 url
209+ body
136210 labels(last: 100) {{
137211 nodes {{
138212 name
@@ -149,6 +223,7 @@ fn get_issues_by_milestone(version: &str, repo_name: &'static str) -> Vec<json::
149223 }}"# ,
150224 repo_name = repo_name,
151225 version = version,
226+ ty = ty,
152227 args = args
153228 . iter( )
154229 . map( |( k, v) | format!( "{}: {}" , k, v) )
@@ -170,9 +245,7 @@ fn get_issues_by_milestone(version: &str, repo_name: &'static str) -> Vec<json::
170245 . send ( )
171246 . unwrap ( ) ;
172247 let status = response. status ( ) ;
173- let json = response
174- . json :: < json:: Value > ( )
175- . unwrap ( ) ;
248+ let json = response. json :: < json:: Value > ( ) . unwrap ( ) ;
176249 if !status. is_success ( ) {
177250 panic ! ( "API Error {}: {}" , status, json) ;
178251 }
@@ -184,7 +257,7 @@ fn get_issues_by_milestone(version: &str, repo_name: &'static str) -> Vec<json::
184257 "More than one milestone matched the query \" {version}\" . Please be more specific." ,
185258 version = version
186259 ) ;
187- let pull_requests_data = milestones_data[ "nodes" ] [ 0 ] [ "pullRequests" ] . clone ( ) ;
260+ let pull_requests_data = milestones_data[ "nodes" ] [ 0 ] [ ty ] . clone ( ) ;
188261
189262 let mut pull_requests = pull_requests_data[ "nodes" ] . as_array ( ) . unwrap ( ) . clone ( ) ;
190263 issues. append ( & mut pull_requests) ;
@@ -212,45 +285,146 @@ fn request_header() -> HeaderMap {
212285 headers
213286}
214287
215- fn map_to_line_items < ' a > ( iter : impl IntoIterator < Item = & ' a json:: Value > ) -> String {
216- iter. into_iter ( )
217- . map ( |o| {
218- format ! (
219- "- [{title}]({url}/)" ,
220- title = o[ "title" ] . as_str( ) . unwrap( ) ,
221- url = o[ "url" ] . as_str( ) . unwrap( ) ,
222- )
223- } )
224- . collect :: < Vec < _ > > ( )
225- . join ( "\n " )
288+ struct TrackingIssues {
289+ // Maps the issue/PR number *tracked* by the issue in `json::Value`.
290+ //
291+ // bool is tracking whether we've used that issue already.
292+ issues : HashMap < u64 , TrackingIssue > ,
226293}
227294
228- fn partition_by_tag < ' a > (
229- iter : impl IntoIterator < Item = & ' a json:: Value > ,
230- tags : & [ & str ] ,
231- ) -> ( JsonRefArray < ' a > , JsonRefArray < ' a > ) {
232- iter. into_iter ( ) . partition ( |o| {
233- o[ "labels" ] [ "nodes" ]
234- . as_array ( )
235- . unwrap ( )
236- . iter ( )
237- . any ( |o| tags. iter ( ) . any ( |tag| o[ "name" ] == * tag) )
238- } )
295+ #[ derive( Debug ) ]
296+ struct TrackingIssue {
297+ raw : json:: Value ,
298+ // Section name -> (used, lines)
299+ sections : HashMap < String , ( bool , Vec < String > ) > ,
300+ }
301+
302+ impl TrackingIssues {
303+ fn collect ( all : & [ json:: Value ] ) -> Self {
304+ let prefix = "Tracking issue for release notes of #" ;
305+ let mut tracking_issues = HashMap :: new ( ) ;
306+ for o in all. iter ( ) {
307+ let title = o[ "title" ] . as_str ( ) . unwrap ( ) ;
308+ if let Some ( tail) = title. strip_prefix ( prefix) {
309+ let for_number = tail[ ..tail. find ( ':' ) . unwrap ( ) ] . parse :: < u64 > ( ) . unwrap ( ) ;
310+ let mut sections = HashMap :: new ( ) ;
311+ let body = o[ "body" ] . as_str ( ) . unwrap ( ) ;
312+ let relnotes = body
313+ . split ( "```" )
314+ . nth ( 1 )
315+ . unwrap ( )
316+ . strip_prefix ( "markdown" )
317+ . unwrap ( ) ;
318+ let mut in_section = None ;
319+ for line in relnotes. lines ( ) {
320+ if line. trim ( ) . is_empty ( ) {
321+ continue ;
322+ }
323+
324+ if let Some ( header) = line. strip_prefix ( "# " ) {
325+ in_section = Some ( header) ;
326+ continue ;
327+ }
328+
329+ if let Some ( section) = in_section {
330+ sections
331+ . entry ( section. to_owned ( ) )
332+ . or_insert_with ( || ( false , vec ! [ ] ) )
333+ . 1
334+ . push ( line. to_owned ( ) ) ;
335+ }
336+ }
337+ tracking_issues. insert (
338+ for_number,
339+ TrackingIssue {
340+ raw : o. clone ( ) ,
341+ sections,
342+ } ,
343+ ) ;
344+ }
345+ }
346+ Self {
347+ issues : tracking_issues,
348+ }
349+ }
239350}
240351
241- fn partition_prs < ' a > (
352+ fn map_to_line_items < ' a > (
242353 iter : impl IntoIterator < Item = & ' a json:: Value > ,
243- ) -> ( String , String , String , String , String ) {
244- let ( compat_notes, rest) = partition_by_tag ( iter, & [ "C-future-compatibility" ] ) ;
245- let ( libs, rest) = partition_by_tag ( rest, & [ "T-libs" , "T-libs-api" ] ) ;
246- let ( lang, rest) = partition_by_tag ( rest, & [ "T-lang" ] ) ;
247- let ( compiler, rest) = partition_by_tag ( rest, & [ "T-compiler" ] ) ;
354+ tracking_issues : & mut TrackingIssues ,
355+ by_section : & mut HashMap < & ' static str , String > ,
356+ ) {
357+ for o in iter {
358+ let title = o[ "title" ] . as_str ( ) . unwrap ( ) ;
359+ if title. starts_with ( "Tracking issue for release notes of #" ) {
360+ continue ;
361+ }
362+ let number = o[ "number" ] . as_u64 ( ) . unwrap ( ) ;
363+
364+ if let Some ( issue) = tracking_issues. issues . get_mut ( & number) {
365+ for ( section, ( used, lines) ) in issue. sections . iter_mut ( ) {
366+ if let Some ( contents) = by_section. get_mut ( section. as_str ( ) ) {
367+ * used = true ;
368+ for line in lines. iter ( ) {
369+ contents. push_str ( line) ;
370+ contents. push ( '\n' ) ;
371+ }
372+ }
373+ }
248374
375+ // If we have a dedicated tracking issue, don't use our default rules.
376+ continue ;
377+ }
378+
379+ // In the future we expect to have increasingly few things fall into this category, as
380+ // things are added to the dedicated tracking issue category in triagebot (today mostly
381+ // FCPs are missing).
382+
383+ let section = if has_tags ( o, & [ "C-future-compatibility" ] ) {
384+ "Compatibility Notes"
385+ } else if has_tags ( o, & [ "T-libs" , "T-libs-api" ] ) {
386+ "Library"
387+ } else if has_tags ( o, & [ "T-lang" ] ) {
388+ "Language"
389+ } else if has_tags ( o, & [ "T-compiler" ] ) {
390+ "Compiler"
391+ } else {
392+ "Other"
393+ } ;
394+ by_section. get_mut ( section) . unwrap ( ) . push_str ( & format ! (
395+ "- [{title}]({url}/)\n " ,
396+ title = o[ "title" ] . as_str( ) . unwrap( ) ,
397+ url = o[ "url" ] . as_str( ) . unwrap( ) ,
398+ ) ) ;
399+ }
400+ }
401+
402+ fn has_tags < ' a > ( o : & ' a json:: Value , tags : & [ & str ] ) -> bool {
403+ o[ "labels" ] [ "nodes" ]
404+ . as_array ( )
405+ . unwrap ( )
406+ . iter ( )
407+ . any ( |o| tags. iter ( ) . any ( |tag| o[ "name" ] == * tag) )
408+ }
409+
410+ fn to_sections < ' a > (
411+ iter : impl IntoIterator < Item = & ' a json:: Value > ,
412+ mut tracking : & mut TrackingIssues ,
413+ ) -> ( String , String , String , String , String , String ) {
414+ let mut by_section = HashMap :: new ( ) ;
415+ by_section. insert ( "Compatibility Notes" , String :: new ( ) ) ;
416+ by_section. insert ( "Library" , String :: new ( ) ) ;
417+ by_section. insert ( "Language" , String :: new ( ) ) ;
418+ by_section. insert ( "Compiler" , String :: new ( ) ) ;
419+ by_section. insert ( "Internal Changes" , String :: new ( ) ) ;
420+ by_section. insert ( "Other" , String :: new ( ) ) ;
421+ map_to_line_items ( iter, & mut tracking, & mut by_section) ;
249422 (
250- map_to_line_items ( compat_notes) ,
251- map_to_line_items ( libs) ,
252- map_to_line_items ( lang) ,
253- map_to_line_items ( compiler) ,
254- map_to_line_items ( rest) ,
423+ by_section. remove ( "Compatibility Notes" ) . unwrap ( ) ,
424+ by_section. remove ( "Library" ) . unwrap ( ) ,
425+ by_section. remove ( "Language" ) . unwrap ( ) ,
426+ by_section. remove ( "Compiler" ) . unwrap ( ) ,
427+ by_section. remove ( "Internal Changes" ) . unwrap ( ) ,
428+ by_section. remove ( "Other" ) . unwrap ( ) ,
255429 )
256430}
0 commit comments