1+ //! Handles stable, beta backports for PRs fixing P-high/critical regressions
2+ //!
3+ //! Add proper labels, opens a poll on Zulip to gauge interest about a backport.
4+ //! Posts a closing messages on Zulip when the PR has been backport accepted.
5+ //!
6+ //! Configuration is done with the `[backport]` table.
7+ //!
18use std:: collections:: HashMap ;
29use std:: sync:: LazyLock ;
310
411use crate :: config:: BackportConfig ;
512use crate :: github:: { IssuesAction , IssuesEvent , Label } ;
613use crate :: handlers:: Context ;
14+ use crate :: utils:: contains_any;
715use anyhow:: Context as AnyhowContext ;
816use futures:: future:: join_all;
917use regex:: Regex ;
@@ -15,12 +23,9 @@ static CLOSES_ISSUE_REGEXP: LazyLock<Regex> = LazyLock::new(|| {
1523 Regex :: new ( "(?i)(?P<action>close[sd]*|fix([e]*[sd]*)?|resolve[sd]*)(?P<spaces>:? +)(?P<org_repo>[a-zA-Z0-9_-]*/[a-zA-Z0-9_-]*)?#(?P<issue_num>[0-9]+)" ) . unwrap ( )
1624} ) ;
1725
18- const BACKPORT_LABELS : [ & str ; 4 ] = [
19- "beta-nominated" ,
20- "beta-accepted" ,
21- "stable-nominated" ,
22- "stable-accepted" ,
23- ] ;
26+ const BACKPORT_ACCEPTED_LABELS : [ & str ; 2 ] = [ "beta-accepted" , "stable-accepted" ] ;
27+
28+ const BACKPORT_NOMINATED_LABELS : [ & str ; 2 ] = [ "beta-nominated" , "stable-nominated" ] ;
2429
2530const REGRESSION_LABELS : [ & str ; 3 ] = [
2631 "regression-from-stable-to-nightly" ,
@@ -53,13 +58,17 @@ pub(super) async fn parse_input(
5358 // - is opened (and not a draft)
5459 // - is converted from draft to ready for review
5560 // - when the first comment is edited
61+ // - when a label is added (later we check which one)
5662 let skip_check = !matches ! (
5763 event. action,
58- IssuesAction :: Opened | IssuesAction :: Edited | IssuesAction :: ReadyForReview
64+ IssuesAction :: Opened
65+ | IssuesAction :: Edited
66+ | IssuesAction :: ReadyForReview
67+ | IssuesAction :: Labeled { label: _ }
5968 ) ;
6069 if skip_check || !event. issue . is_pr ( ) || event. issue . draft {
6170 log:: debug!(
62- "Skipping backport event because: IssuesAction = {:?}, issue.is_pr() {}, draft = {}" ,
71+ "Skipping backport event because: IssuesAction = {:?}, issue.is_pr() = {}, draft = {}" ,
6372 event. action,
6473 event. issue. is_pr( ) ,
6574 event. issue. draft
@@ -69,9 +78,37 @@ pub(super) async fn parse_input(
6978 let pr = & event. issue ;
7079
7180 let pr_labels: Vec < & str > = pr. labels . iter ( ) . map ( |l| l. name . as_str ( ) ) . collect ( ) ;
72- if contains_any ( & pr_labels, & BACKPORT_LABELS ) {
73- log:: debug!( "PR #{} already has a backport label" , pr. number) ;
74- return Ok ( None ) ;
81+
82+ // if adding a "-nominated" label to a PR, proceed if PR does not have it already
83+ // (we don't check for "-accepted" labels)
84+ if let IssuesAction :: Labeled { label } = & event. action
85+ && BACKPORT_NOMINATED_LABELS . contains ( & label. name . as_str ( ) )
86+ {
87+ if contains_any ( & pr_labels, & BACKPORT_NOMINATED_LABELS ) {
88+ log:: debug!(
89+ "PR #{} is already backport nominated (found labels: {:?})" ,
90+ pr. number,
91+ pr_labels
92+ ) ;
93+ // NOTE: this won't revert the label added by github
94+ return Ok ( None ) ;
95+ }
96+ }
97+
98+ // If adding an "-accepted" label to a PR, proceed if PR does not have it already
99+ // (we skip checking the "-nominated" labels, sometimes T-release straight backport accepts PRs)
100+ if let IssuesAction :: Labeled { label } = & event. action
101+ && BACKPORT_ACCEPTED_LABELS . contains ( & label. name . as_str ( ) )
102+ {
103+ if contains_any ( & pr_labels, & BACKPORT_ACCEPTED_LABELS ) {
104+ log:: debug!(
105+ "PR #{} is already backport accepted (found labels: {:?})" ,
106+ pr. number,
107+ pr_labels
108+ ) ;
109+ // NOTE: this won't revert the label added by github
110+ return Ok ( None ) ;
111+ }
75112 }
76113
77114 // Retrieve backport config for this PR, based on its team label(s)
@@ -187,7 +224,8 @@ pub(super) async fn handle_input(
187224 continue ;
188225 }
189226
190- // Add backport nomination label(s) to PR
227+ // When backport nominating/accepting a PR, if a `[notify-zulip]` table is configured in triagebot.toml
228+ // that will trigger the notify_zulip handler
191229 let mut new_labels = pr. labels ( ) . to_owned ( ) ;
192230 new_labels. extend (
193231 add_labels
@@ -210,16 +248,12 @@ pub(super) async fn handle_input(
210248 Ok ( ( ) )
211249}
212250
213- fn contains_any ( haystack : & [ & str ] , needles : & [ & str ] ) -> bool {
214- needles. iter ( ) . any ( |needle| haystack. contains ( needle) )
215- }
216-
217251#[ cfg( test) ]
218252mod tests {
219253 use crate :: handlers:: backport:: CLOSES_ISSUE_REGEXP ;
220254
221- #[ tokio :: test]
222- async fn backport_match_comment ( ) {
255+ #[ test]
256+ fn backport_match_comment ( ) {
223257 let test_strings = vec ! [
224258 ( "close #10" , vec![ 10 ] ) ,
225259 ( "closes #10" , vec![ 10 ] ) ,
0 commit comments