@@ -17,20 +17,47 @@ use crate::utils::pluralize;
1717use crate :: zulip:: api:: { MessageApiResponse , Recipient } ;
1818use crate :: zulip:: client:: ZulipClient ;
1919use crate :: zulip:: commands:: {
20- ChatCommand , LookupCmd , PingGoalsArgs , StreamCommand , WorkqueueCmd , WorkqueueLimit , parse_cli,
20+ BackportChannelArgs , BackportVerbArgs , ChatCommand , LookupCmd , PingGoalsArgs , StreamCommand ,
21+ WorkqueueCmd , WorkqueueLimit , parse_cli,
2122} ;
2223use anyhow:: { Context as _, format_err} ;
2324use axum:: Json ;
2425use axum:: extract:: State ;
2526use axum:: extract:: rejection:: JsonRejection ;
2627use axum:: response:: IntoResponse ;
28+ use commands:: BackportArgs ;
2729use itertools:: Itertools ;
30+ use octocrab:: Octocrab ;
2831use rust_team_data:: v1:: { TeamKind , TeamMember } ;
2932use std:: cmp:: Reverse ;
3033use std:: fmt:: Write as _;
3134use std:: sync:: Arc ;
3235use subtle:: ConstantTimeEq ;
33- use tracing as log;
36+ use tracing:: log;
37+
38+ fn get_text_backport_approved (
39+ channel : & BackportChannelArgs ,
40+ verb : & BackportVerbArgs ,
41+ zulip_link : & str ,
42+ ) -> String {
43+ format ! ( "
44+ {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 are handled by them.
45+
46+ @rustbot label +{channel}-accepted" )
47+ }
48+
49+ fn get_text_backport_declined (
50+ channel : & BackportChannelArgs ,
51+ verb : & BackportVerbArgs ,
52+ zulip_link : & str ,
53+ ) -> String {
54+ format ! (
55+ "
56+ {channel} backport {verb} as per compiler team [on Zulip]({zulip_link}).
57+
58+ @rustbot label -{channel}-nominated -{channel}-accepted"
59+ )
60+ }
3461
3562#[ derive( Debug , serde:: Deserialize ) ]
3663pub struct Request {
@@ -317,6 +344,9 @@ async fn handle_command<'a>(
317344 ping_goals_cmd ( ctx, gh_id, message_data, & args) . await
318345 }
319346 StreamCommand :: DocsUpdate => trigger_docs_update ( message_data, & ctx. zulip ) ,
347+ StreamCommand :: Backport ( args) => {
348+ accept_decline_backport ( message_data, & ctx. octocrab , & ctx. zulip , & args) . await
349+ }
320350 } ;
321351 }
322352
@@ -325,6 +355,57 @@ async fn handle_command<'a>(
325355 }
326356}
327357
358+ // TODO: shorter variant of this command (f.e. `backport accept` or even `accept`) that infers everything from the Message payload
359+ async fn accept_decline_backport (
360+ message_data : & Message ,
361+ octo_client : & Octocrab ,
362+ zulip_client : & ZulipClient ,
363+ args_data : & BackportArgs ,
364+ ) -> anyhow:: Result < Option < String > > {
365+ let message = message_data. clone ( ) ;
366+ let args = args_data. clone ( ) ;
367+ let stream_id = message. stream_id . unwrap ( ) ;
368+ let subject = message. subject . unwrap ( ) ;
369+
370+ // Repository owner and name are hardcoded
371+ // This command is only used in this repository
372+ let repo_owner = "rust-lang" ;
373+ let repo_name = "rust" ;
374+
375+ // TODO: factor out the Zulip "URL encoder" to make it practical to use
376+ let zulip_send_req = crate :: zulip:: MessageApiRequest {
377+ recipient : Recipient :: Stream {
378+ id : stream_id,
379+ topic : & subject,
380+ } ,
381+ content : "" ,
382+ } ;
383+
384+ // NOTE: the Zulip Message API cannot yet pin exactly a single message so the link in the GitHub comment will be to the whole topic
385+ // See: https://rust-lang.zulipchat.com/#narrow/channel/122653-zulip/topic/.22near.22.20parameter.20in.20payload.20of.20send.20message.20API
386+ let zulip_link = zulip_send_req. url ( zulip_client) ;
387+
388+ let message_body = match args. verb {
389+ BackportVerbArgs :: Accept
390+ | BackportVerbArgs :: Accepted
391+ | BackportVerbArgs :: Approve
392+ | BackportVerbArgs :: Approved => {
393+ get_text_backport_approved ( & args. channel , & args. verb , & zulip_link)
394+ }
395+ BackportVerbArgs :: Decline | BackportVerbArgs :: Declined => {
396+ get_text_backport_declined ( & args. channel , & args. verb , & zulip_link)
397+ }
398+ } ;
399+
400+ let _ = octo_client
401+ . issues ( repo_owner, repo_name)
402+ . create_comment ( args. pr_num , & message_body)
403+ . await
404+ . with_context ( || anyhow:: anyhow!( "unable to post comment on #{}" , args. pr_num) ) ?;
405+
406+ Ok ( None )
407+ }
408+
328409async fn ping_goals_cmd (
329410 ctx : Arc < Context > ,
330411 gh_id : u64 ,
0 commit comments