review-classifier: skip draft prs, classify once when they leave draft
This commit is contained in:
parent
65d75da8db
commit
49ef93a19e
4 changed files with 280 additions and 34 deletions
|
|
@ -157,6 +157,9 @@ pub struct PullForReview {
|
|||
/// Why `diff` is the forge's `.diff`, with 3 lines of context, rather
|
||||
/// than [`pull_diff::wide`]'s. `None` when it is the wide one.
|
||||
pub narrowed: Option<String>,
|
||||
/// The forge calls the PR a draft (a title starting with a
|
||||
/// work-in-progress prefix).
|
||||
pub draft: bool,
|
||||
}
|
||||
|
||||
/// Files on a repo's branch, as [`Client::default_branch_files`] and
|
||||
|
|
@ -1382,6 +1385,7 @@ impl Client {
|
|||
.await
|
||||
.with_context(|| format!("read PR {number} of {owner}/{repo}"))?;
|
||||
let changed_files = pull.changed_files;
|
||||
let draft = pull.draft.unwrap_or(false);
|
||||
let head_sha = pull
|
||||
.head
|
||||
.and_then(|h| h.sha)
|
||||
|
|
@ -1443,6 +1447,7 @@ impl Client {
|
|||
commits,
|
||||
diff,
|
||||
narrowed,
|
||||
draft,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -870,6 +870,7 @@ pub(crate) mod tests {
|
|||
commits: Vec::new(),
|
||||
diff: String::new(),
|
||||
narrowed: self.narrowed.clone(),
|
||||
draft: false,
|
||||
})
|
||||
} else {
|
||||
Err(ForgejoError::ApiError(ApiErrorKind::NotFound { errors: None }.into()).into())
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ pub(super) enum Request {
|
|||
Started,
|
||||
/// Taken for a head already reviewed; its verdict was posted again.
|
||||
Reposted,
|
||||
/// Taken for a PR the forge calls a draft; nothing ran or posted.
|
||||
Draft,
|
||||
}
|
||||
|
||||
impl Request {
|
||||
|
|
@ -43,6 +45,7 @@ impl Request {
|
|||
Self::Deferred => "deferred",
|
||||
Self::Started => "started",
|
||||
Self::Reposted => "reposted",
|
||||
Self::Draft => "draft",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -678,6 +678,16 @@ async fn review(
|
|||
slot: Slot,
|
||||
) {
|
||||
let pull = read_pull(forge, req).await;
|
||||
if matches!(&pull, Ok(pull) if pull.draft) {
|
||||
drop(slot);
|
||||
metrics.request(Request::Draft);
|
||||
tracing::info!(
|
||||
repo = %format!("{}/{}", req.owner, req.name), pr = req.pr,
|
||||
head_sha = req.head.as_str(),
|
||||
"review requested on a draft PR; skipping it"
|
||||
);
|
||||
return;
|
||||
}
|
||||
let moved = matches!(&pull, Ok(pull) if !req.head.is_empty() && pull.head_sha != req.head);
|
||||
if moved {
|
||||
tracing::info!(
|
||||
|
|
@ -1650,7 +1660,8 @@ mod tests {
|
|||
/// [`Heads::move_to`] moves it.
|
||||
struct Heads {
|
||||
fake: Fake,
|
||||
heads: Mutex<std::collections::HashMap<i64, String>>,
|
||||
head_of: Mutex<std::collections::HashMap<i64, String>>,
|
||||
drafts: Mutex<std::collections::HashSet<i64>>,
|
||||
}
|
||||
|
||||
impl Heads {
|
||||
|
|
@ -1662,12 +1673,17 @@ mod tests {
|
|||
},
|
||||
vec![rules_json()],
|
||||
),
|
||||
heads: Mutex::default(),
|
||||
head_of: Mutex::default(),
|
||||
drafts: Mutex::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn make_draft(&self, pr: i64) {
|
||||
self.drafts.lock().expect("unpoisoned").insert(pr);
|
||||
}
|
||||
|
||||
fn move_to(&self, pr: i64, head: &str) {
|
||||
self.heads
|
||||
self.head_of
|
||||
.lock()
|
||||
.expect("unpoisoned")
|
||||
.insert(pr, head.to_owned());
|
||||
|
|
@ -1682,12 +1698,18 @@ mod tests {
|
|||
number: i64,
|
||||
) -> anyhow::Result<PullForReview> {
|
||||
anyhow::ensure!((owner, repo) == ("o", "r"), "no such repo");
|
||||
let head = self.heads.lock().expect("unpoisoned").get(&number).cloned();
|
||||
let head = self
|
||||
.head_of
|
||||
.lock()
|
||||
.expect("unpoisoned")
|
||||
.get(&number)
|
||||
.cloned();
|
||||
Ok(PullForReview {
|
||||
head_sha: head.unwrap_or_else(|| "abc123".to_owned()),
|
||||
commits: Vec::new(),
|
||||
diff: String::new(),
|
||||
narrowed: None,
|
||||
draft: self.drafts.lock().expect("unpoisoned").contains(&number),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -2019,6 +2041,26 @@ mod tests {
|
|||
assert_eq!(q.rig.gate.reached(), 2, "no run after the ack");
|
||||
}
|
||||
|
||||
/// A request taken from the queue for a PR that is a draft by then runs
|
||||
/// nothing and posts nothing; the other PR in the queue is reviewed.
|
||||
#[tokio::test]
|
||||
async fn a_queued_request_for_a_draft_pr_is_skipped() {
|
||||
let q = Queued::new().await;
|
||||
q.rig.forge.make_draft(7);
|
||||
q.request(7, "abc123").await;
|
||||
q.request(8, "abc123").await;
|
||||
|
||||
let _consumer = q.consume(&q.rig.reviews);
|
||||
q.rig.gate.open(2);
|
||||
until("the other PR is reviewed", || q.rig.posted().len() == 1).await;
|
||||
tokio::time::sleep(TEST_TIMING.ack_wait * 2).await;
|
||||
let posted = q.rig.posted();
|
||||
assert_eq!(posted.len(), 1, "the draft posts nothing");
|
||||
assert_eq!(posted[0].0, "o/r#8");
|
||||
assert_eq!(q.rig.gate.reached(), 1, "the draft starts no run");
|
||||
assert_eq!(q.waiting().await, 0, "the draft request is acked, not kept");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn a_newer_request_for_a_pr_replaces_the_waiting_one() {
|
||||
let q = Queued::new().await;
|
||||
|
|
|
|||
Loading…
Reference in a new issue