@@ -13,7 +13,7 @@ use crate::handlers::docs_update::docs_update;
13
13
use crate :: handlers:: pr_tracking:: get_assigned_prs;
14
14
use crate :: handlers:: project_goals:: { self , ping_project_goals_owners} ;
15
15
use crate :: interactions:: ErrorComment ;
16
- use crate :: utils:: pluralize;
16
+ use crate :: utils:: { contains_any , pluralize} ;
17
17
use crate :: zulip:: api:: { MessageApiResponse , Recipient } ;
18
18
use crate :: zulip:: client:: ZulipClient ;
19
19
use crate :: zulip:: commands:: {
@@ -24,12 +24,45 @@ use axum::Json;
24
24
use axum:: extract:: State ;
25
25
use axum:: extract:: rejection:: JsonRejection ;
26
26
use axum:: response:: IntoResponse ;
27
+ use commands:: BackportArgs ;
28
+ use octocrab:: Octocrab ;
27
29
use rust_team_data:: v1:: { TeamKind , TeamMember } ;
28
30
use std:: cmp:: Reverse ;
29
31
use std:: fmt:: Write as _;
30
32
use std:: sync:: Arc ;
31
33
use subtle:: ConstantTimeEq ;
32
- use tracing as log;
34
+ use tracing:: log;
35
+
36
+ // const BACKPORT_APPROVED: &str = "
37
+ // {args.channel} backport {args.verb} as per compiler team [on Zulip]({zulip_link}). A backport PR will be authored by the release team at the end of the current development cycle. Backport labels handled by them.
38
+
39
+ // @rustbot label +{args.channel}-accepted
40
+ // ";
41
+ // const BACKPORT_DECLINED: &str = "
42
+ // {args.channel} backport {args.verb} as per compiler team [on Zulip]({zulip_link}).
43
+
44
+ // @rustbot label -{args.channel}-nominated
45
+ // ";
46
+
47
+ fn get_text_backport_approved ( channel : & str , verb : & str , zulip_link : & str ) -> String {
48
+ format ! ( "
49
+ {channel} backport {verb} as per compiler team [on Zulip]({zulip_link}). A backport PR will be authored by the release team at the end of the current development cycle. Backport labels handled by them.
50
+
51
+ @rustbot label +{channel}-accepted" )
52
+ }
53
+
54
+ fn get_text_backport_declined ( channel : & str , verb : & str , zulip_link : & str ) -> String {
55
+ format ! (
56
+ "
57
+ {channel} backport {verb} as per compiler team [on Zulip]({zulip_link}).
58
+
59
+ @rustbot label -{channel}-nominated"
60
+ )
61
+ }
62
+
63
+ const BACKPORT_CHANNELS : [ & str ; 2 ] = [ "beta" , "stable" ] ;
64
+ const BACKPORT_VERBS_APPROVE : [ & str ; 4 ] = [ "accept" , "accepted" , "approve" , "approved" ] ;
65
+ const BACKPORT_VERBS_DECLINE : [ & str ; 2 ] = [ "decline" , "declined" ] ;
33
66
34
67
#[ derive( Debug , serde:: Deserialize ) ]
35
68
pub struct Request {
@@ -302,10 +335,72 @@ async fn handle_command<'a>(
302
335
. map_err ( |e| format_err ! ( "Failed to await at this time: {e:?}" ) ) ,
303
336
StreamCommand :: PingGoals ( args) => ping_goals_cmd ( ctx, gh_id, message_data, & args) . await ,
304
337
StreamCommand :: DocsUpdate => trigger_docs_update ( message_data, & ctx. zulip ) ,
338
+ StreamCommand :: Backport ( args) => {
339
+ accept_decline_backport ( message_data, & ctx. octocrab , & ctx. zulip , & args) . await
340
+ }
305
341
}
306
342
}
307
343
}
308
344
345
+ // TODO: shorter variant of this command (f.e. `backport accept` or even `accept`) that infers everything from the Message payload
346
+ async fn accept_decline_backport (
347
+ message_data : & Message ,
348
+ octo_client : & Octocrab ,
349
+ zulip_client : & ZulipClient ,
350
+ args_data : & BackportArgs ,
351
+ ) -> anyhow:: Result < Option < String > > {
352
+ let message = message_data. clone ( ) ;
353
+ let args = args_data. clone ( ) ;
354
+ let stream_id = message. stream_id . unwrap ( ) ;
355
+ let subject = message. subject . unwrap ( ) ;
356
+ let verb = args. verb . to_lowercase ( ) ;
357
+ let octo_client = octo_client. clone ( ) ;
358
+
359
+ // Repository owner and name are hardcoded
360
+ // This command is only used in this repository
361
+ let repo_owner = "rust-lang" ;
362
+ let repo_name = "rust" ;
363
+
364
+ // validate command parameters
365
+ if !contains_any ( & [ args. channel . to_lowercase ( ) . as_str ( ) ] , & BACKPORT_CHANNELS ) {
366
+ return Err ( anyhow:: anyhow!(
367
+ "Parser error: unknown channel (allowed: {BACKPORT_CHANNELS:?})."
368
+ ) ) ;
369
+ }
370
+
371
+ // TODO: factor out the Zulip "URL encoder" to make it practical to use
372
+ let zulip_send_req = crate :: zulip:: MessageApiRequest {
373
+ recipient : Recipient :: Stream {
374
+ id : stream_id,
375
+ topic : & subject,
376
+ } ,
377
+ content : "" ,
378
+ } ;
379
+ let zulip_link = zulip_send_req. url ( zulip_client) ;
380
+
381
+ let message_body = if contains_any ( & [ verb. as_str ( ) ] , & BACKPORT_VERBS_APPROVE ) {
382
+ get_text_backport_approved ( & args. channel , & verb, & zulip_link)
383
+ } else if contains_any ( & [ verb. as_str ( ) ] , & BACKPORT_VERBS_DECLINE ) {
384
+ get_text_backport_declined ( & args. channel , & verb, & zulip_link)
385
+ } else {
386
+ return Err ( anyhow:: anyhow!(
387
+ "Parser error: unknown verb (allowed: {BACKPORT_VERBS_APPROVE:?} or {BACKPORT_VERBS_DECLINE:?})"
388
+ ) ) ;
389
+ } ;
390
+
391
+ tokio:: spawn ( async move {
392
+ let res = octo_client
393
+ . issues ( repo_owner, repo_name)
394
+ . create_comment ( args. pr_num , & message_body)
395
+ . await
396
+ . context ( "unable to post comment on #{args.pr_num}" ) ;
397
+ if res. is_err ( ) {
398
+ tracing:: error!( "failed to post comment: {0:?}" , res. err( ) ) ;
399
+ }
400
+ } ) ;
401
+ Ok ( Some ( "" . to_string ( ) ) )
402
+ }
403
+
309
404
async fn ping_goals_cmd (
310
405
ctx : Arc < Context > ,
311
406
gh_id : u64 ,
0 commit comments