@@ -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) ;
@@ -745,49 +749,25 @@ impl Issue {
745
749
Ok ( ( ) )
746
750
}
747
751
752
+ /// Sets the milestone of the issue or PR.
753
+ ///
754
+ /// This will create the milestone if it does not exist. The new milestone
755
+ /// will start in the "open" state.
748
756
pub async fn set_milestone ( & self , client : & GithubClient , title : & str ) -> anyhow:: Result < ( ) > {
749
757
log:: trace!(
750
758
"Setting milestone for rust-lang/rust#{} to {}" ,
751
759
self . number,
752
760
title
753
761
) ;
754
762
755
- let create_url = format ! ( "{}/milestones" , self . repository( ) . url( ) ) ;
756
- let resp = client
757
- . send_req (
758
- client
759
- . post ( & create_url)
760
- . body ( serde_json:: to_vec ( & MilestoneCreateBody { title } ) . unwrap ( ) ) ,
761
- )
762
- . await ;
763
- // Explicitly do *not* try to return Err(...) if this fails -- that's
764
- // fine, it just means the milestone was already created.
765
- log:: trace!( "Created milestone: {:?}" , resp) ;
766
-
767
- let list_url = format ! ( "{}/milestones" , self . repository( ) . url( ) ) ;
768
- let milestone_list: Vec < Milestone > = client. json ( client. get ( & list_url) ) . await ?;
769
- let milestone_no = if let Some ( milestone) = milestone_list. iter ( ) . find ( |v| v. title == title)
770
- {
771
- milestone. number
772
- } else {
773
- anyhow:: bail!(
774
- "Despite just creating milestone {} on {}, it does not exist?" ,
775
- title,
776
- self . repository( )
777
- )
778
- } ;
763
+ let full_repo_name = self . repository ( ) . full_repo_name ( ) ;
764
+ let milestone = client
765
+ . get_or_create_milestone ( & full_repo_name, title, "open" )
766
+ . await ?;
779
767
780
- #[ derive( serde:: Serialize ) ]
781
- struct SetMilestone {
782
- milestone : u64 ,
783
- }
784
- let url = format ! ( "{}/issues/{}" , self . repository( ) . url( ) , self . number) ;
785
768
client
786
- . send_req ( client. patch ( & url) . json ( & SetMilestone {
787
- milestone : milestone_no,
788
- } ) )
789
- . await
790
- . context ( "failed to set milestone" ) ?;
769
+ . set_milestone ( & full_repo_name, & milestone, self . number )
770
+ . await ?;
791
771
Ok ( ( ) )
792
772
}
793
773
@@ -886,11 +866,6 @@ pub struct PullRequestFile {
886
866
pub blob_url : String ,
887
867
}
888
868
889
- #[ derive( serde:: Serialize ) ]
890
- struct MilestoneCreateBody < ' a > {
891
- title : & ' a str ,
892
- }
893
-
894
869
#[ derive( Debug , serde:: Deserialize ) ]
895
870
pub struct Milestone {
896
871
number : u64 ,
@@ -1246,6 +1221,33 @@ impl Repository {
1246
1221
)
1247
1222
}
1248
1223
1224
+ /// Returns a list of commits between the SHA ranges of start (exclusive)
1225
+ /// and end (inclusive).
1226
+ pub async fn commits_in_range (
1227
+ & self ,
1228
+ client : & GithubClient ,
1229
+ start : & str ,
1230
+ end : & str ,
1231
+ ) -> anyhow:: Result < Vec < GithubCommit > > {
1232
+ let mut commits = Vec :: new ( ) ;
1233
+ let mut page = 1 ;
1234
+ loop {
1235
+ let url = format ! ( "{}/commits?sha={end}&per_page=100&page={page}" , self . url( ) ) ;
1236
+ let mut this_page: Vec < GithubCommit > = client
1237
+ . json ( client. get ( & url) )
1238
+ . await
1239
+ . with_context ( || format ! ( "failed to fetch commits for {url}" ) ) ?;
1240
+ if let Some ( idx) = this_page. iter ( ) . position ( |commit| commit. sha == start) {
1241
+ this_page. truncate ( idx) ;
1242
+ commits. extend ( this_page) ;
1243
+ return Ok ( commits) ;
1244
+ } else {
1245
+ commits. extend ( this_page) ;
1246
+ }
1247
+ page += 1 ;
1248
+ }
1249
+ }
1250
+
1249
1251
/// Retrieves a git commit for the given SHA.
1250
1252
pub async fn git_commit ( & self , client : & GithubClient , sha : & str ) -> anyhow:: Result < GitCommit > {
1251
1253
let url = format ! ( "{}/git/commits/{sha}" , self . url( ) ) ;
@@ -1616,6 +1618,40 @@ impl Repository {
1616
1618
} ) ?;
1617
1619
Ok ( ( ) )
1618
1620
}
1621
+
1622
+ /// Get or create a [`Milestone`].
1623
+ ///
1624
+ /// This will not change the state if it already exists.
1625
+ pub async fn get_or_create_milestone (
1626
+ & self ,
1627
+ client : & GithubClient ,
1628
+ title : & str ,
1629
+ state : & str ,
1630
+ ) -> anyhow:: Result < Milestone > {
1631
+ client
1632
+ . get_or_create_milestone ( & self . full_name , title, state)
1633
+ . await
1634
+ }
1635
+
1636
+ /// Set the milestone of an issue or PR.
1637
+ pub async fn set_milestone (
1638
+ & self ,
1639
+ client : & GithubClient ,
1640
+ milestone : & Milestone ,
1641
+ issue_num : u64 ,
1642
+ ) -> anyhow:: Result < ( ) > {
1643
+ client
1644
+ . set_milestone ( & self . full_name , milestone, issue_num)
1645
+ . await
1646
+ }
1647
+
1648
+ pub async fn get_issue ( & self , client : & GithubClient , issue_num : u64 ) -> anyhow:: Result < Issue > {
1649
+ let url = format ! ( "{}/pulls/{issue_num}" , self . url( ) ) ;
1650
+ client
1651
+ . json ( client. get ( & url) )
1652
+ . await
1653
+ . with_context ( || format ! ( "{} failed to get issue {issue_num}" , self . full_name) )
1654
+ }
1619
1655
}
1620
1656
1621
1657
pub struct Query < ' a > {
@@ -2126,6 +2162,83 @@ impl GithubClient {
2126
2162
. await
2127
2163
. with_context ( || format ! ( "{} failed to get repo" , full_name) )
2128
2164
}
2165
+
2166
+ /// Get or create a [`Milestone`].
2167
+ ///
2168
+ /// This will not change the state if it already exists.
2169
+ async fn get_or_create_milestone (
2170
+ & self ,
2171
+ full_repo_name : & str ,
2172
+ title : & str ,
2173
+ state : & str ,
2174
+ ) -> anyhow:: Result < Milestone > {
2175
+ let url = format ! (
2176
+ "{}/repos/{full_repo_name}/milestones" ,
2177
+ Repository :: GITHUB_API_URL
2178
+ ) ;
2179
+ let resp = self
2180
+ . send_req ( self . post ( & url) . json ( & serde_json:: json!( {
2181
+ "title" : title,
2182
+ "state" : state,
2183
+ } ) ) )
2184
+ . await ;
2185
+ match resp {
2186
+ Ok ( ( body, _dbg) ) => {
2187
+ let milestone = serde_json:: from_slice ( & body) ?;
2188
+ log:: trace!( "Created milestone: {milestone:?}" ) ;
2189
+ return Ok ( milestone) ;
2190
+ }
2191
+ Err ( e) => {
2192
+ if e. downcast_ref :: < reqwest:: Error > ( ) . map_or ( false , |e| {
2193
+ matches ! ( e. status( ) , Some ( StatusCode :: UNPROCESSABLE_ENTITY ) )
2194
+ } ) {
2195
+ // fall-through, it already exists
2196
+ } else {
2197
+ return Err ( e. context ( format ! (
2198
+ "failed to create milestone {url} with title {title}"
2199
+ ) ) ) ;
2200
+ }
2201
+ }
2202
+ }
2203
+ // In the case where it already exists, we need to search for its number.
2204
+ let mut page = 1 ;
2205
+ loop {
2206
+ let url = format ! (
2207
+ "{}/repos/{full_repo_name}/milestones?page={page}&state=all" ,
2208
+ Repository :: GITHUB_API_URL
2209
+ ) ;
2210
+ let milestones: Vec < Milestone > = self
2211
+ . json ( self . get ( & url) )
2212
+ . await
2213
+ . with_context ( || format ! ( "failed to get milestones {url} searching for {title}" ) ) ?;
2214
+ if milestones. is_empty ( ) {
2215
+ anyhow:: bail!( "expected to find milestone with title {title}" ) ;
2216
+ }
2217
+ if let Some ( milestone) = milestones. into_iter ( ) . find ( |m| m. title == title) {
2218
+ return Ok ( milestone) ;
2219
+ }
2220
+ page += 1 ;
2221
+ }
2222
+ }
2223
+
2224
+ /// Set the milestone of an issue or PR.
2225
+ async fn set_milestone (
2226
+ & self ,
2227
+ full_repo_name : & str ,
2228
+ milestone : & Milestone ,
2229
+ issue_num : u64 ,
2230
+ ) -> anyhow:: Result < ( ) > {
2231
+ let url = format ! (
2232
+ "{}/repos/{full_repo_name}/issues/{issue_num}" ,
2233
+ Repository :: GITHUB_API_URL
2234
+ ) ;
2235
+ self . send_req ( self . patch ( & url) . json ( & serde_json:: json!( {
2236
+ "milestone" : milestone. number
2237
+ } ) ) )
2238
+ . await
2239
+ . with_context ( || format ! ( "failed to set milestone for {url} to milestone {milestone:?}" ) ) ?;
2240
+ Ok ( ( ) )
2241
+ }
2129
2242
}
2130
2243
2131
2244
#[ derive( Debug , serde:: Deserialize ) ]
0 commit comments