Skip to content

Commit 2e35c7b

Browse files
authored
Merge pull request #432 from Sakib25800/failed-count
Add failed count to queue stats
2 parents e92b8d2 + c6d36b3 commit 2e35c7b

File tree

7 files changed

+193
-133
lines changed

7 files changed

+193
-133
lines changed

src/bors/handlers/retry.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::PgDbClient;
44
use crate::bors::handlers::{PullRequestData, deny_request, has_permission};
55
use crate::bors::merge_queue::MergeQueueSender;
66
use crate::bors::{Comment, RepositoryState};
7+
use crate::database::QueueStatus;
78
use crate::github::{GithubUser, PullRequestNumber};
89
use crate::permissions::PermissionType;
910

@@ -21,7 +22,7 @@ pub(super) async fn command_retry(
2122

2223
let pr_model = pr.db;
2324

24-
if pr_model.is_stalled() {
25+
if matches!(pr_model.queue_status(), QueueStatus::Stalled(_, _)) {
2526
db.clear_auto_build(pr_model).await?;
2627
merge_queue_tx.notify().await?;
2728
} else {

src/bors/handlers/workflow.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ use crate::bors::handlers::{BuildType, is_bors_observed_branch};
66
use crate::bors::handlers::{get_build_type, hide_try_build_started_comments};
77
use crate::bors::merge_queue::MergeQueueSender;
88
use crate::bors::{FailedWorkflowRun, RepositoryState, WorkflowRun};
9-
use crate::database::{BuildModel, BuildStatus, PullRequestModel, WorkflowModel, WorkflowStatus};
9+
use crate::database::{
10+
BuildModel, BuildStatus, PullRequestModel, QueueStatus, WorkflowModel, WorkflowStatus,
11+
};
1012
use crate::github::api::client::GithubRepositoryClient;
1113
use crate::github::{CommitSha, LabelTrigger};
1214
use octocrab::models::CheckRunId;
@@ -407,16 +409,14 @@ pub async fn maybe_cancel_auto_build(
407409
pr: &PullRequestModel,
408410
reason: AutoBuildCancelReason,
409411
) -> anyhow::Result<Option<String>> {
410-
let Some(auto_build) = &pr.auto_build else {
411-
return Ok(None);
412+
let auto_build = match pr.queue_status() {
413+
QueueStatus::Pending(_, build) => build,
414+
_ => return Ok(None),
412415
};
413-
if auto_build.status != BuildStatus::Pending {
414-
return Ok(None);
415-
}
416416

417417
tracing::info!("Cancelling auto build {auto_build:?}");
418418

419-
match cancel_build(client, db, auto_build, CheckRunConclusion::Cancelled).await {
419+
match cancel_build(client, db, &auto_build, CheckRunConclusion::Cancelled).await {
420420
Ok(workflows) => {
421421
tracing::info!("Auto build cancelled");
422422
let workflow_urls = workflows.into_iter().map(|w| w.url).collect();

src/bors/merge_queue.rs

Lines changed: 114 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,13 @@ use crate::bors::comment::{
1212
merge_conflict_comment,
1313
};
1414
use crate::bors::{PullRequestStatus, RepositoryState};
15-
use crate::database::{BuildStatus, MergeableState, OctocrabMergeableState, PullRequestModel};
15+
use crate::database::{
16+
ApprovalInfo, BuildModel, BuildStatus, MergeableState, OctocrabMergeableState,
17+
PullRequestModel, QueueStatus,
18+
};
1619
use crate::github::api::client::CheckRunOutput;
1720
use crate::github::api::operations::{BranchUpdateError, ForcePush};
18-
use crate::github::{CommitSha, PullRequest};
21+
use crate::github::{CommitSha, PullRequest, PullRequestNumber};
1922
use crate::github::{MergeResult, attempt_merge};
2023
use crate::utils::sort_queue::sort_queue_prs;
2124

@@ -130,114 +133,126 @@ async fn process_repository(repo: &RepositoryState, ctx: &BorsContext) -> anyhow
130133
for pr in prs {
131134
let pr_num = pr.number;
132135

133-
let Some(auto_build) = &pr.auto_build else {
134-
// No build exists for this PR - start a new auto build.
135-
match start_auto_build(repo, ctx, &pr).await {
136-
Ok(()) => {
137-
tracing::info!("Starting auto build for PR {pr_num}");
138-
break;
139-
}
140-
Err(StartAutoBuildError::MergeConflict) => {
141-
let gh_pr = repo.client.get_pull_request(pr.number).await?;
142-
143-
tracing::debug!(
144-
"Failed to start auto build for PR {pr_num} due to merge conflict"
145-
);
146-
147-
ctx.db
148-
.update_pr_mergeable_state(&pr, MergeableState::Unknown)
149-
.await?;
150-
repo.client
151-
.post_comment(pr.number, merge_conflict_comment(&gh_pr.head.name))
152-
.await?;
153-
continue;
154-
}
155-
Err(StartAutoBuildError::SanityCheckFailed(error)) => {
156-
tracing::info!("Sanity check failed for PR {pr_num}: {error:?}");
157-
break;
158-
}
159-
Err(StartAutoBuildError::GitHubError(error)) => {
160-
tracing::debug!(
161-
"Failed to start auto build for PR {pr_num} due to a GitHub error: {error:?}"
162-
);
163-
break;
164-
}
165-
Err(StartAutoBuildError::DatabaseError(error)) => {
166-
tracing::debug!(
167-
"Failed to start auto build for PR {pr_num} due to database error: {error:?}"
168-
);
169-
break;
170-
}
171-
}
172-
};
173-
174-
let commit_sha = CommitSha(auto_build.commit_sha.clone());
175-
176-
match auto_build.status {
177-
// Build successful - point the base branch to the merged commit.
178-
BuildStatus::Success => {
179-
let workflows = ctx.db.get_workflows_for_build(auto_build).await?;
180-
let comment = auto_build_succeeded_comment(
181-
&workflows,
182-
pr.approver().unwrap_or("<unknown>"),
183-
&commit_sha,
184-
&pr.base_branch,
185-
);
186-
187-
if let Err(error) = repo
188-
.client
189-
.set_branch_to_sha(&pr.base_branch, &commit_sha, ForcePush::No)
190-
.await
191-
{
192-
tracing::error!(
193-
"Failed to fast-forward base branch for PR {pr_num}: {error:?}"
194-
);
195-
196-
let comment = match &error {
197-
BranchUpdateError::Conflict(branch_name) => auto_build_push_failed_comment(
198-
&format!("this PR has conflicts with the `{branch_name}` branch"),
199-
),
200-
BranchUpdateError::ValidationFailed(branch_name) => {
201-
auto_build_push_failed_comment(&format!(
202-
"the tested commit was behind the `{branch_name}` branch"
203-
))
204-
}
205-
error => auto_build_push_failed_comment(&error.to_string()),
206-
};
207-
208-
ctx.db
209-
.update_build_status(auto_build, BuildStatus::Failure)
210-
.await?;
211-
212-
repo.client.post_comment(pr_num, comment).await?;
213-
} else {
214-
tracing::info!("Auto build succeeded and merged for PR {pr_num}");
215-
216-
ctx.db
217-
.set_pr_status(&pr.repository, pr.number, PullRequestStatus::Merged)
218-
.await?;
219-
220-
repo.client.post_comment(pr.number, comment).await?;
221-
}
222-
223-
// Break to give GitHub time to update the base branch.
136+
match pr.queue_status() {
137+
QueueStatus::NotApproved => unreachable!(),
138+
QueueStatus::Stalled(..) => unreachable!(),
139+
QueueStatus::Pending(..) => {
140+
// Build in progress - stop queue. We can only have one PR being built
141+
// at a time.
142+
tracing::info!("PR {pr_num} has a pending build - blocking queue");
224143
break;
225144
}
226-
// Build in progress - stop queue. We can only have one PR being built
227-
// at a time.
228-
BuildStatus::Pending => {
229-
tracing::info!("PR {pr_num} has a pending build - blocking queue");
145+
QueueStatus::ReadyForMerge(approval_info, auto_build) => {
146+
handle_successful_build(repo, ctx, &pr, &auto_build, &approval_info, pr_num)
147+
.await?;
230148
break;
231149
}
232-
BuildStatus::Failure | BuildStatus::Cancelled | BuildStatus::Timeouted => {
233-
unreachable!("Failed auto builds should be filtered out by SQL query");
150+
QueueStatus::Approved(..) => {
151+
if handle_start_auto_build(repo, ctx, &pr, pr_num).await? {
152+
break;
153+
}
234154
}
235155
}
236156
}
237157

238158
Ok(())
239159
}
240160

161+
/// Handle a successful auto build by pointing the base branch to the merged commit.
162+
async fn handle_successful_build(
163+
repo: &RepositoryState,
164+
ctx: &BorsContext,
165+
pr: &PullRequestModel,
166+
auto_build: &BuildModel,
167+
approval_info: &ApprovalInfo,
168+
pr_num: PullRequestNumber,
169+
) -> anyhow::Result<()> {
170+
let commit_sha = CommitSha(auto_build.commit_sha.clone());
171+
let workflows = ctx.db.get_workflows_for_build(auto_build).await?;
172+
let comment = auto_build_succeeded_comment(
173+
&workflows,
174+
&approval_info.approver,
175+
&commit_sha,
176+
&pr.base_branch,
177+
);
178+
179+
if let Err(error) = repo
180+
.client
181+
.set_branch_to_sha(&pr.base_branch, &commit_sha, ForcePush::No)
182+
.await
183+
{
184+
tracing::error!("Failed to fast-forward base branch for PR {pr_num}: {error:?}");
185+
186+
let error_comment = match &error {
187+
BranchUpdateError::Conflict(branch_name) => auto_build_push_failed_comment(&format!(
188+
"this PR has conflicts with the `{branch_name}` branch"
189+
)),
190+
BranchUpdateError::ValidationFailed(branch_name) => auto_build_push_failed_comment(
191+
&format!("the tested commit was behind the `{branch_name}` branch"),
192+
),
193+
error => auto_build_push_failed_comment(&error.to_string()),
194+
};
195+
196+
ctx.db
197+
.update_build_status(auto_build, BuildStatus::Failure)
198+
.await?;
199+
repo.client.post_comment(pr_num, error_comment).await?;
200+
} else {
201+
tracing::info!("Auto build succeeded and merged for PR {pr_num}");
202+
ctx.db
203+
.set_pr_status(&pr.repository, pr.number, PullRequestStatus::Merged)
204+
.await?;
205+
repo.client.post_comment(pr.number, comment).await?;
206+
}
207+
208+
Ok(())
209+
}
210+
211+
/// Handle starting a new auto build for an approved PR.
212+
/// Returns true if the queue should break, false to continue.
213+
async fn handle_start_auto_build(
214+
repo: &RepositoryState,
215+
ctx: &BorsContext,
216+
pr: &PullRequestModel,
217+
pr_num: PullRequestNumber,
218+
) -> anyhow::Result<bool> {
219+
let Err(error) = start_auto_build(repo, ctx, pr).await else {
220+
tracing::info!("Starting auto build for PR {pr_num}");
221+
return Ok(true);
222+
};
223+
224+
match error {
225+
StartAutoBuildError::MergeConflict => {
226+
let gh_pr = repo.client.get_pull_request(pr.number).await?;
227+
tracing::debug!("Failed to start auto build for PR {pr_num} due to merge conflict");
228+
229+
ctx.db
230+
.update_pr_mergeable_state(pr, MergeableState::Unknown)
231+
.await?;
232+
repo.client
233+
.post_comment(pr.number, merge_conflict_comment(&gh_pr.head.name))
234+
.await?;
235+
Ok(false)
236+
}
237+
StartAutoBuildError::SanityCheckFailed(error) => {
238+
tracing::info!("Sanity check failed for PR {pr_num}: {error:?}");
239+
Ok(true)
240+
}
241+
StartAutoBuildError::GitHubError(error) => {
242+
tracing::debug!(
243+
"Failed to start auto build for PR {pr_num} due to a GitHub error: {error:?}"
244+
);
245+
Ok(true)
246+
}
247+
StartAutoBuildError::DatabaseError(error) => {
248+
tracing::debug!(
249+
"Failed to start auto build for PR {pr_num} due to database error: {error:?}"
250+
);
251+
Ok(true)
252+
}
253+
}
254+
}
255+
241256
#[must_use]
242257
pub enum StartAutoBuildError {
243258
/// Merge conflict between PR head and base branch.

src/database/mod.rs

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,20 @@ pub struct ApprovalInfo {
181181
pub sha: String,
182182
}
183183

184+
#[derive(Debug, Clone, PartialEq)]
185+
pub enum QueueStatus {
186+
/// Approved with running auto build.
187+
Pending(ApprovalInfo, BuildModel),
188+
/// Approved with failed auto build.
189+
Stalled(ApprovalInfo, BuildModel),
190+
/// Approved with no auto build started yet or a failed auto build was reset
191+
/// with `@bors retry`.
192+
Approved(ApprovalInfo),
193+
/// Approved with passing CI.
194+
ReadyForMerge(ApprovalInfo, BuildModel),
195+
NotApproved,
196+
}
197+
184198
/// Represents the approval status of a pull request.
185199
#[derive(Debug, Clone, PartialEq)]
186200
pub enum ApprovalStatus {
@@ -271,7 +285,7 @@ impl FromStr for DelegatedPermission {
271285
}
272286

273287
/// Status of a GitHub build.
274-
#[derive(Debug, PartialEq, sqlx::Type)]
288+
#[derive(Debug, Clone, PartialEq, sqlx::Type)]
275289
#[sqlx(type_name = "TEXT")]
276290
#[sqlx(rename_all = "lowercase")]
277291
pub enum BuildStatus {
@@ -309,7 +323,7 @@ impl Display for BuildStatus {
309323
}
310324

311325
/// Represents a single (merged) commit.
312-
#[derive(Debug, sqlx::Type)]
326+
#[derive(Debug, Clone, PartialEq, sqlx::Type)]
313327
#[sqlx(type_name = "build")]
314328
pub struct BuildModel {
315329
pub id: PrimaryKey,
@@ -382,13 +396,25 @@ impl PullRequestModel {
382396
}
383397
}
384398

385-
/// Approved but with a failed auto build.
386-
pub fn is_stalled(&self) -> bool {
387-
self.is_approved()
388-
&& self
389-
.auto_build
390-
.as_ref()
391-
.is_some_and(|build| build.status.is_failure())
399+
/// Get the merge queue status of this pull request.
400+
pub fn queue_status(&self) -> QueueStatus {
401+
match &self.approval_status {
402+
ApprovalStatus::NotApproved => QueueStatus::NotApproved,
403+
ApprovalStatus::Approved(approval_info) => match &self.auto_build {
404+
Some(build) => match build.status {
405+
BuildStatus::Pending => {
406+
QueueStatus::Pending(approval_info.clone(), build.clone())
407+
}
408+
BuildStatus::Success => {
409+
QueueStatus::ReadyForMerge(approval_info.clone(), build.clone())
410+
}
411+
BuildStatus::Failure | BuildStatus::Cancelled | BuildStatus::Timeouted => {
412+
QueueStatus::Stalled(approval_info.clone(), build.clone())
413+
}
414+
},
415+
None => QueueStatus::Approved(approval_info.clone()),
416+
},
417+
}
392418
}
393419
}
394420

0 commit comments

Comments
 (0)