@@ -426,6 +426,10 @@ impl IssueRepository {
426
426
)
427
427
}
428
428
429
+ fn full_repo_name ( & self ) -> String {
430
+ format ! ( "{}/{}" , self . organization, self . repository)
431
+ }
432
+
429
433
async fn has_label ( & self , client : & GithubClient , label : & str ) -> anyhow:: Result < bool > {
430
434
#[ allow( clippy:: redundant_pattern_matching) ]
431
435
let url = format ! ( "{}/labels/{}" , self . url( ) , label) ;
@@ -760,49 +764,25 @@ impl Issue {
760
764
Ok ( ( ) )
761
765
}
762
766
767
+ /// Sets the milestone of the issue or PR.
768
+ ///
769
+ /// This will create the milestone if it does not exist. The new milestone
770
+ /// will start in the "open" state.
763
771
pub async fn set_milestone ( & self , client : & GithubClient , title : & str ) -> anyhow:: Result < ( ) > {
764
772
log:: trace!(
765
773
"Setting milestone for rust-lang/rust#{} to {}" ,
766
774
self . number,
767
775
title
768
776
) ;
769
777
770
- let create_url = format ! ( "{}/milestones" , self . repository( ) . url( ) ) ;
771
- let resp = client
772
- . send_req (
773
- client
774
- . post ( & create_url)
775
- . body ( serde_json:: to_vec ( & MilestoneCreateBody { title } ) . unwrap ( ) ) ,
776
- )
777
- . await ;
778
- // Explicitly do *not* try to return Err(...) if this fails -- that's
779
- // fine, it just means the milestone was already created.
780
- log:: trace!( "Created milestone: {:?}" , resp) ;
781
-
782
- let list_url = format ! ( "{}/milestones" , self . repository( ) . url( ) ) ;
783
- let milestone_list: Vec < Milestone > = client. json ( client. get ( & list_url) ) . await ?;
784
- let milestone_no = if let Some ( milestone) = milestone_list. iter ( ) . find ( |v| v. title == title)
785
- {
786
- milestone. number
787
- } else {
788
- anyhow:: bail!(
789
- "Despite just creating milestone {} on {}, it does not exist?" ,
790
- title,
791
- self . repository( )
792
- )
793
- } ;
778
+ let full_repo_name = self . repository ( ) . full_repo_name ( ) ;
779
+ let milestone = client
780
+ . get_or_create_milestone ( & full_repo_name, title, "open" )
781
+ . await ?;
794
782
795
- #[ derive( serde:: Serialize ) ]
796
- struct SetMilestone {
797
- milestone : u64 ,
798
- }
799
- let url = format ! ( "{}/issues/{}" , self . repository( ) . url( ) , self . number) ;
800
783
client
801
- . send_req ( client. patch ( & url) . json ( & SetMilestone {
802
- milestone : milestone_no,
803
- } ) )
804
- . await
805
- . context ( "failed to set milestone" ) ?;
784
+ . set_milestone ( & full_repo_name, & milestone, self . number )
785
+ . await ?;
806
786
Ok ( ( ) )
807
787
}
808
788
@@ -901,11 +881,6 @@ pub struct PullRequestFile {
901
881
pub blob_url : String ,
902
882
}
903
883
904
- #[ derive( serde:: Serialize ) ]
905
- struct MilestoneCreateBody < ' a > {
906
- title : & ' a str ,
907
- }
908
-
909
884
#[ derive( Debug , serde:: Deserialize ) ]
910
885
pub struct Milestone {
911
886
number : u64 ,
@@ -1261,6 +1236,33 @@ impl Repository {
1261
1236
)
1262
1237
}
1263
1238
1239
+ /// Returns a list of commits between the SHA ranges of start (exclusive)
1240
+ /// and end (inclusive).
1241
+ pub async fn commits_in_range (
1242
+ & self ,
1243
+ client : & GithubClient ,
1244
+ start : & str ,
1245
+ end : & str ,
1246
+ ) -> anyhow:: Result < Vec < GithubCommit > > {
1247
+ let mut commits = Vec :: new ( ) ;
1248
+ let mut page = 1 ;
1249
+ loop {
1250
+ let url = format ! ( "{}/commits?sha={end}&per_page=100&page={page}" , self . url( ) ) ;
1251
+ let mut this_page: Vec < GithubCommit > = client
1252
+ . json ( client. get ( & url) )
1253
+ . await
1254
+ . with_context ( || format ! ( "failed to fetch commits for {url}" ) ) ?;
1255
+ if let Some ( idx) = this_page. iter ( ) . position ( |commit| commit. sha == start) {
1256
+ this_page. truncate ( idx) ;
1257
+ commits. extend ( this_page) ;
1258
+ return Ok ( commits) ;
1259
+ } else {
1260
+ commits. extend ( this_page) ;
1261
+ }
1262
+ page += 1 ;
1263
+ }
1264
+ }
1265
+
1264
1266
/// Retrieves a git commit for the given SHA.
1265
1267
pub async fn git_commit ( & self , client : & GithubClient , sha : & str ) -> anyhow:: Result < GitCommit > {
1266
1268
let url = format ! ( "{}/git/commits/{sha}" , self . url( ) ) ;
@@ -1631,6 +1633,40 @@ impl Repository {
1631
1633
} ) ?;
1632
1634
Ok ( ( ) )
1633
1635
}
1636
+
1637
+ /// Get or create a [`Milestone`].
1638
+ ///
1639
+ /// This will not change the state if it already exists.
1640
+ pub async fn get_or_create_milestone (
1641
+ & self ,
1642
+ client : & GithubClient ,
1643
+ title : & str ,
1644
+ state : & str ,
1645
+ ) -> anyhow:: Result < Milestone > {
1646
+ client
1647
+ . get_or_create_milestone ( & self . full_name , title, state)
1648
+ . await
1649
+ }
1650
+
1651
+ /// Set the milestone of an issue or PR.
1652
+ pub async fn set_milestone (
1653
+ & self ,
1654
+ client : & GithubClient ,
1655
+ milestone : & Milestone ,
1656
+ issue_num : u64 ,
1657
+ ) -> anyhow:: Result < ( ) > {
1658
+ client
1659
+ . set_milestone ( & self . full_name , milestone, issue_num)
1660
+ . await
1661
+ }
1662
+
1663
+ pub async fn get_issue ( & self , client : & GithubClient , issue_num : u64 ) -> anyhow:: Result < Issue > {
1664
+ let url = format ! ( "{}/pulls/{issue_num}" , self . url( ) ) ;
1665
+ client
1666
+ . json ( client. get ( & url) )
1667
+ . await
1668
+ . with_context ( || format ! ( "{} failed to get issue {issue_num}" , self . full_name) )
1669
+ }
1634
1670
}
1635
1671
1636
1672
pub struct Query < ' a > {
@@ -2141,6 +2177,83 @@ impl GithubClient {
2141
2177
. await
2142
2178
. with_context ( || format ! ( "{} failed to get repo" , full_name) )
2143
2179
}
2180
+
2181
+ /// Get or create a [`Milestone`].
2182
+ ///
2183
+ /// This will not change the state if it already exists.
2184
+ async fn get_or_create_milestone (
2185
+ & self ,
2186
+ full_repo_name : & str ,
2187
+ title : & str ,
2188
+ state : & str ,
2189
+ ) -> anyhow:: Result < Milestone > {
2190
+ let url = format ! (
2191
+ "{}/repos/{full_repo_name}/milestones" ,
2192
+ Repository :: GITHUB_API_URL
2193
+ ) ;
2194
+ let resp = self
2195
+ . send_req ( self . post ( & url) . json ( & serde_json:: json!( {
2196
+ "title" : title,
2197
+ "state" : state,
2198
+ } ) ) )
2199
+ . await ;
2200
+ match resp {
2201
+ Ok ( ( body, _dbg) ) => {
2202
+ let milestone = serde_json:: from_slice ( & body) ?;
2203
+ log:: trace!( "Created milestone: {milestone:?}" ) ;
2204
+ return Ok ( milestone) ;
2205
+ }
2206
+ Err ( e) => {
2207
+ if e. downcast_ref :: < reqwest:: Error > ( ) . map_or ( false , |e| {
2208
+ matches ! ( e. status( ) , Some ( StatusCode :: UNPROCESSABLE_ENTITY ) )
2209
+ } ) {
2210
+ // fall-through, it already exists
2211
+ } else {
2212
+ return Err ( e. context ( format ! (
2213
+ "failed to create milestone {url} with title {title}"
2214
+ ) ) ) ;
2215
+ }
2216
+ }
2217
+ }
2218
+ // In the case where it already exists, we need to search for its number.
2219
+ let mut page = 1 ;
2220
+ loop {
2221
+ let url = format ! (
2222
+ "{}/repos/{full_repo_name}/milestones?page={page}&state=all" ,
2223
+ Repository :: GITHUB_API_URL
2224
+ ) ;
2225
+ let milestones: Vec < Milestone > = self
2226
+ . json ( self . get ( & url) )
2227
+ . await
2228
+ . with_context ( || format ! ( "failed to get milestones {url} searching for {title}" ) ) ?;
2229
+ if milestones. is_empty ( ) {
2230
+ anyhow:: bail!( "expected to find milestone with title {title}" ) ;
2231
+ }
2232
+ if let Some ( milestone) = milestones. into_iter ( ) . find ( |m| m. title == title) {
2233
+ return Ok ( milestone) ;
2234
+ }
2235
+ page += 1 ;
2236
+ }
2237
+ }
2238
+
2239
+ /// Set the milestone of an issue or PR.
2240
+ async fn set_milestone (
2241
+ & self ,
2242
+ full_repo_name : & str ,
2243
+ milestone : & Milestone ,
2244
+ issue_num : u64 ,
2245
+ ) -> anyhow:: Result < ( ) > {
2246
+ let url = format ! (
2247
+ "{}/repos/{full_repo_name}/issues/{issue_num}" ,
2248
+ Repository :: GITHUB_API_URL
2249
+ ) ;
2250
+ self . send_req ( self . patch ( & url) . json ( & serde_json:: json!( {
2251
+ "milestone" : milestone. number
2252
+ } ) ) )
2253
+ . await
2254
+ . with_context ( || format ! ( "failed to set milestone for {url} to milestone {milestone:?}" ) ) ?;
2255
+ Ok ( ( ) )
2256
+ }
2144
2257
}
2145
2258
2146
2259
#[ derive( Debug , serde:: Deserialize ) ]
0 commit comments