Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 41 additions & 19 deletions src/handlers/backport.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
//! Handle backport labels for PRs fixing P-high/critical regressions
//!
//! Configuration is done with the `[backport]` table.
//!
use std::collections::HashMap;
use std::sync::LazyLock;

use crate::config::BackportConfig;
use crate::github::{IssuesAction, IssuesEvent, Label};
use crate::handlers::Context;
use crate::utils::contains_any;
use anyhow::Context as AnyhowContext;
use futures::future::join_all;
use regex::Regex;
Expand All @@ -15,12 +20,9 @@ static CLOSES_ISSUE_REGEXP: LazyLock<Regex> = LazyLock::new(|| {
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()
});

const BACKPORT_LABELS: [&str; 4] = [
"beta-nominated",
"beta-accepted",
"stable-nominated",
"stable-accepted",
];
const BACKPORT_ACCEPTED_LABELS: [&str; 2] = ["beta-accepted", "stable-accepted"];

const BACKPORT_NOMINATED_LABELS: [&str; 2] = ["beta-nominated", "stable-nominated"];

const REGRESSION_LABELS: [&str; 3] = [
"regression-from-stable-to-nightly",
Expand All @@ -41,7 +43,7 @@ pub(crate) struct BackportInput {
}

pub(super) async fn parse_input(
_ctx: &Context,
ctx: &Context,
event: &IssuesEvent,
config: Option<&BackportConfig>,
) -> Result<Option<BackportInput>, String> {
Expand All @@ -53,13 +55,17 @@ pub(super) async fn parse_input(
// - is opened (and not a draft)
// - is converted from draft to ready for review
// - when the first comment is edited
// - when a label is added (later we check which one)
let skip_check = !matches!(
event.action,
IssuesAction::Opened | IssuesAction::Edited | IssuesAction::ReadyForReview
IssuesAction::Opened
| IssuesAction::Edited
| IssuesAction::ReadyForReview
| IssuesAction::Labeled { label: _ }
);
if skip_check || !event.issue.is_pr() || event.issue.draft {
log::debug!(
"Skipping backport event because: IssuesAction = {:?}, issue.is_pr() {}, draft = {}",
"Skipping backport event because: IssuesAction = {:?}, issue.is_pr() = {}, draft = {}",
event.action,
event.issue.is_pr(),
event.issue.draft
Expand All @@ -69,9 +75,28 @@ pub(super) async fn parse_input(
let pr = &event.issue;

let pr_labels: Vec<&str> = pr.labels.iter().map(|l| l.name.as_str()).collect();
if contains_any(&pr_labels, &BACKPORT_LABELS) {
log::debug!("PR #{} already has a backport label", pr.number);
return Ok(None);

// If a "-nominated" label is added to an already "-accepted" PR, remove the label added by github
if let IssuesAction::Labeled { label } = &event.action
&& BACKPORT_NOMINATED_LABELS.contains(&label.name.as_str())
{
if contains_any(&pr_labels, &BACKPORT_ACCEPTED_LABELS) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will have the same bug as before, right? If the PR is already beta-accepted, it will stop the handler here once stable-accepted is added.

You'll need to separate the labels on the axis of beta/stable, rather than nominated/accepted.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm ... you're right but the solution is more complex that separating on another axis. I'm afraid I'll need to map all possible cases (4 labels = 16 cases) and then see if I can reduce to a couple of ifs.

In any case, I want to keep it simple: if this does not work, I have another patch to add "negated labels" when sending zulip notification, that would solve the issue from another point of view.

I'll be back on this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't just remove this:

if contains_any(&pr_labels, &BACKPORT_LABELS) {
    log::debug!("PR #{} already has a backport label", pr.number);
    return Ok(None);
}

? Seems like the bug is just caused by this.

Btw, I tested that once you receive the Labeled { label: "foo" } event, then pr.labels() already contains foo.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry, I am confused by your comment: isn't that chunk of code removed in this patch? Or did you mean to just remove that code without adding anything else?

In the latter case, I think that not checking labels would open up to confusion when adding regression labels (which was the point of this patch).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I meant remove without adding anything else. Say that we remove the original check - what will be the problematic situations that could be caused in that case? Adding -nominated once there is already -accepted? Or something else? I don't know what is a "regression label", it wasn't mentioned in this PR so far 😅

log::debug!(
"Refusing to backport nominate, PR #{} is already backport accepted (found labels: {:?})",
pr.number,
pr_labels
);
let label_to_remove = vec![Label {
name: label.name.clone(),
}];
let _ = &event
.issue
.remove_labels(&ctx.github, label_to_remove)
.await
.context("failed to remove labels from the issue")
.unwrap();
return Ok(None);
}
}

// Retrieve backport config for this PR, based on its team label(s)
Expand Down Expand Up @@ -187,7 +212,8 @@ pub(super) async fn handle_input(
continue;
}

// Add backport nomination label(s) to PR
// When backport nominating/accepting a PR, if a `[notify-zulip]` table is configured in triagebot.toml
// that will trigger the notify_zulip handler
let mut new_labels = pr.labels().to_owned();
new_labels.extend(
add_labels
Expand All @@ -210,16 +236,12 @@ pub(super) async fn handle_input(
Ok(())
}

fn contains_any(haystack: &[&str], needles: &[&str]) -> bool {
needles.iter().any(|needle| haystack.contains(needle))
}

#[cfg(test)]
mod tests {
use crate::handlers::backport::CLOSES_ISSUE_REGEXP;

#[tokio::test]
async fn backport_match_comment() {
#[test]
fn backport_match_comment() {
let test_strings = vec![
("close #10", vec![10]),
("closes #10", vec![10]),
Expand Down
4 changes: 4 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,7 @@ pub(crate) async fn is_issue_under_rfcbot_fcp(

false
}

pub fn contains_any(haystack: &[&str], needles: &[&str]) -> bool {
needles.iter().any(|needle| haystack.contains(needle))
}