Skip to content

Commit fae3e53

Browse files
authored
Merge pull request #529 from Kobzol/pause-resume
Implement pause/resume functionality
2 parents 424a36e + 0f4b895 commit fae3e53

File tree

9 files changed

+121
-23
lines changed

9 files changed

+121
-23
lines changed

src/bors/command/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,4 +132,9 @@ pub enum BorsCommand {
132132
/// Clear a failed auto build status from an approved PR.
133133
/// This will cause the merge queue to attempt to start a new auto build and retry merging the PR again.
134134
Retry,
135+
/// Pause the merge queue and related functionality ([un]approving, delegation, tree closing,
136+
/// etc.).
137+
Pause,
138+
/// Resume the merge queue and related functionality.
139+
Resume,
135140
}

src/bors/command/parser.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,8 @@ const DEFAULT_PARSERS: &[ParserFn] = &[
144144
parser_ping,
145145
parser_retry,
146146
parser_tree_ops,
147+
parser_pause,
148+
parser_resume,
147149
];
148150

149151
const ONLY_TRY_PARSERS: &[ParserFn] = &[parser_try_cancel, parser_try];
@@ -441,6 +443,22 @@ fn parser_tree_ops(command: &CommandPart<'_>, _parts: &[CommandPart<'_>]) -> Par
441443
}
442444
}
443445

446+
/// Parses `@bors pause` command.
447+
fn parser_pause(command: &CommandPart<'_>, _parts: &[CommandPart<'_>]) -> ParseResult {
448+
match command {
449+
CommandPart::Bare("pause") => Some(Ok(BorsCommand::Pause)),
450+
_ => None,
451+
}
452+
}
453+
454+
/// Parses `@bors resume` command.
455+
fn parser_resume(command: &CommandPart<'_>, _parts: &[CommandPart<'_>]) -> ParseResult {
456+
match command {
457+
CommandPart::Bare("resume") => Some(Ok(BorsCommand::Resume)),
458+
_ => None,
459+
}
460+
}
461+
444462
#[cfg(test)]
445463
mod tests {
446464
use crate::bors::command::parser::{CommandParseError, CommandParser};

src/bors/handlers/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,16 @@ async fn handle_comment(
507507
.instrument(span)
508508
.await
509509
}
510+
BorsCommand::Pause => {
511+
tracing::info!("Pausing {}", repo.repository());
512+
repo.set_paused(true);
513+
Ok(())
514+
}
515+
BorsCommand::Resume => {
516+
tracing::info!("Resuming {}", repo.repository());
517+
repo.set_paused(false);
518+
Ok(())
519+
}
510520
};
511521
if result.is_err() {
512522
return result.context("Cannot execute Bors command");

src/bors/handlers/ping.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@ pub(super) async fn command_ping(
88
repo: Arc<RepositoryState>,
99
pr_number: PullRequestNumber,
1010
) -> anyhow::Result<()> {
11+
let mut msg = "Pong 🏓!".to_string();
12+
if repo.is_paused() {
13+
msg.push_str(" (bors is paused)");
14+
}
1115
repo.client
12-
.post_comment(pr_number, Comment::new("Pong 🏓!".to_string()))
16+
.post_comment(pr_number, Comment::new(msg))
1317
.await?;
1418
Ok(())
1519
}

src/bors/handlers/retry.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ pub(super) async fn command_retry(
2525
if matches!(pr_model.queue_status(), QueueStatus::Failed(_, _)) {
2626
db.clear_auto_build(pr_model).await?;
2727
merge_queue_tx.notify().await?;
28-
} else {
28+
} else if !repo_state.is_paused() {
2929
notify_of_invalid_retry_state(&repo_state, pr.number()).await?;
3030
}
3131

src/bors/handlers/review.rs

Lines changed: 60 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -63,15 +63,19 @@ pub(super) async fn command_approve(
6363
let priority = priority.or(pr.db.priority.map(|p| p as u32));
6464

6565
merge_queue_tx.notify().await?;
66-
handle_label_trigger(
67-
&repo_state,
68-
pr.number(),
69-
Some(pr.github),
70-
LabelTrigger::Approved,
71-
)
72-
.await?;
7366

74-
notify_of_approval(ctx, &repo_state, pr, priority, approver.as_str()).await
67+
if !repo_state.is_paused() {
68+
handle_label_trigger(
69+
&repo_state,
70+
pr.number(),
71+
Some(pr.github),
72+
LabelTrigger::Approved,
73+
)
74+
.await?;
75+
76+
notify_of_approval(ctx, &repo_state, pr, priority, approver.as_str()).await?;
77+
}
78+
Ok(())
7579
}
7680

7781
/// Keywords that will prevent an approval if they appear in the PR's title.
@@ -152,7 +156,10 @@ pub(super) async fn command_unapprove(
152156
)
153157
.await?;
154158
unapprove_pr(&repo_state, &db, pr.db, pr.github).await?;
155-
notify_of_unapproval(&repo_state, pr, auto_build_cancel_message).await?;
159+
160+
if !repo_state.is_paused() {
161+
notify_of_unapproval(&repo_state, pr, auto_build_cancel_message).await?;
162+
}
156163

157164
Ok(())
158165
}
@@ -193,15 +200,19 @@ pub(super) async fn command_delegate(
193200
}
194201

195202
db.delegate(pr.db, delegated_permission).await?;
196-
notify_of_delegation(
197-
&repo_state,
198-
pr.number(),
199-
&pr.github.author.username,
200-
&author.username,
201-
delegated_permission,
202-
bot_prefix,
203-
)
204-
.await
203+
204+
if !repo_state.is_paused() {
205+
notify_of_delegation(
206+
&repo_state,
207+
pr.number(),
208+
&pr.github.author.username,
209+
&author.username,
210+
delegated_permission,
211+
bot_prefix,
212+
)
213+
.await?;
214+
}
215+
Ok(())
205216
}
206217

207218
/// Revoke any previously granted delegation.
@@ -258,7 +269,11 @@ pub(super) async fn command_close_tree(
258269
.await?;
259270

260271
merge_queue_tx.notify().await?;
261-
notify_of_tree_closed(&repo_state, pr.number(), priority).await
272+
273+
if !repo_state.is_paused() {
274+
notify_of_tree_closed(&repo_state, pr.number(), priority).await?;
275+
}
276+
Ok(())
262277
}
263278

264279
pub(super) async fn command_open_tree(
@@ -277,7 +292,11 @@ pub(super) async fn command_open_tree(
277292
.await?;
278293

279294
merge_queue_tx.notify().await?;
280-
notify_of_tree_open(&repo_state, pr.number()).await
295+
296+
if !repo_state.is_paused() {
297+
notify_of_tree_open(&repo_state, pr.number()).await?;
298+
}
299+
Ok(())
281300
}
282301

283302
fn sufficient_approve_permission(repo: Arc<RepositoryState>, author: &GithubUser) -> bool {
@@ -1399,4 +1418,25 @@ labels_blocking_approval = ["proposed-final-comment-period", "final-comment-peri
13991418
})
14001419
.await;
14011420
}
1421+
1422+
#[sqlx::test]
1423+
async fn pause_resume(pool: sqlx::PgPool) {
1424+
run_test(pool, async |ctx: &mut BorsTester| {
1425+
ctx.post_comment("@bors pause").await?;
1426+
ctx.post_comment("@bors r+").await?;
1427+
ctx.wait_for_pr((), |pr| pr.is_approved()).await?;
1428+
ctx.run_merge_queue_now().await;
1429+
ctx.pr(()).await.expect_no_auto_build();
1430+
ctx.post_comment("@bors resume").await?;
1431+
1432+
// Sync
1433+
ctx.post_comment("@bors info").await?;
1434+
ctx.expect_comments((), 1).await;
1435+
1436+
ctx.start_and_finish_auto_build(()).await?;
1437+
1438+
Ok(())
1439+
})
1440+
.await;
1441+
}
14021442
}

src/bors/merge_queue.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ pub async fn merge_queue_tick(ctx: Arc<BorsContext>) -> anyhow::Result<()> {
9898
}
9999

100100
async fn process_repository(repo: &RepositoryState, ctx: &BorsContext) -> anyhow::Result<()> {
101-
if !repo.config.load().merge_queue_enabled {
101+
if !repo.config.load().merge_queue_enabled || repo.is_paused() {
102102
return Ok(());
103103
}
104104

src/bors/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use serde::Serialize;
1919
use std::collections::HashMap;
2020
use std::fmt;
2121
use std::str::FromStr;
22+
use std::sync::atomic::{AtomicBool, Ordering};
2223
use std::sync::{Arc, LazyLock, RwLock};
2324
use std::time::Duration;
2425

@@ -75,6 +76,8 @@ pub fn format_help() -> &'static str {
7576
BorsCommand::OpenTree => {}
7677
BorsCommand::TreeClosed(_) => {}
7778
BorsCommand::Retry => {}
79+
BorsCommand::Pause => {}
80+
BorsCommand::Resume => {}
7881
}
7982

8083
r#"
@@ -180,12 +183,20 @@ pub struct RepositoryState {
180183
pub client: GithubRepositoryClient,
181184
pub permissions: ArcSwap<UserPermissions>,
182185
pub config: ArcSwap<RepositoryConfig>,
186+
pub paused: AtomicBool,
183187
}
184188

185189
impl RepositoryState {
186190
pub fn repository(&self) -> &GithubRepoName {
187191
self.client.repository()
188192
}
193+
194+
pub fn set_paused(&self, state: bool) {
195+
self.paused.store(state, Ordering::SeqCst);
196+
}
197+
pub fn is_paused(&self) -> bool {
198+
self.paused.load(Ordering::SeqCst)
199+
}
189200
}
190201

191202
#[derive(Default)]

src/github/api/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::collections::HashMap;
22
use std::sync::Arc;
3+
use std::sync::atomic::AtomicBool;
34
use std::time::Duration;
45

56
use anyhow::Context;
@@ -161,10 +162,19 @@ async fn create_repo_state(
161162

162163
let config = load_config(&client).await?;
163164

165+
// Start paused for now, as a cautionary measure
166+
let paused = AtomicBool::new(true);
167+
168+
#[cfg(test)]
169+
{
170+
paused.store(false, std::sync::atomic::Ordering::SeqCst);
171+
}
172+
164173
Ok(RepositoryState {
165174
client,
166175
config: ArcSwap::new(Arc::new(config)),
167176
permissions: ArcSwap::new(Arc::new(permissions)),
177+
paused,
168178
})
169179
}
170180

0 commit comments

Comments
 (0)