Skip to content

Commit 754b7f3

Browse files
authored
Merge pull request #120 from vohoanglong0107/feat-unapprove-on-push
feat: unapprove on push
2 parents f77e09a + 03996f3 commit 754b7f3

File tree

8 files changed

+1339
-77
lines changed

8 files changed

+1339
-77
lines changed

src/bors/event.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ use octocrab::models::RunId;
66
pub enum BorsRepositoryEvent {
77
/// A comment was posted on a pull request.
88
Comment(PullRequestComment),
9+
/// When a new commit is pushed to the pull request branch.
10+
PullRequestCommitPushed(PullRequestPushed),
911
/// When the pull request is edited by its author
1012
PullRequestEdited(PullRequestEdited),
1113
/// A workflow run on Github Actions or a check run from external CI system has been started.
@@ -21,10 +23,11 @@ impl BorsRepositoryEvent {
2123
pub fn repository(&self) -> &GithubRepoName {
2224
match self {
2325
BorsRepositoryEvent::Comment(comment) => &comment.repository,
26+
BorsRepositoryEvent::PullRequestCommitPushed(payload) => &payload.repository,
27+
BorsRepositoryEvent::PullRequestEdited(payload) => &payload.repository,
2428
BorsRepositoryEvent::WorkflowStarted(workflow) => &workflow.repository,
2529
BorsRepositoryEvent::WorkflowCompleted(workflow) => &workflow.repository,
2630
BorsRepositoryEvent::CheckSuiteCompleted(payload) => &payload.repository,
27-
BorsRepositoryEvent::PullRequestEdited(payload) => &payload.repository,
2831
}
2932
}
3033
}
@@ -54,6 +57,12 @@ pub struct PullRequestComment {
5457
pub text: String,
5558
}
5659

60+
#[derive(Debug)]
61+
pub struct PullRequestPushed {
62+
pub repository: GithubRepoName,
63+
pub pull_request: PullRequest,
64+
}
65+
5766
#[derive(Debug)]
5867
pub struct PullRequestEdited {
5968
pub repository: GithubRepoName,

src/bors/handlers/mod.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::bors::handlers::help::command_help;
99
use crate::bors::handlers::ping::command_ping;
1010
use crate::bors::handlers::refresh::refresh_repository;
1111
use crate::bors::handlers::review::{
12-
command_approve, command_unapprove, handle_pull_request_edited,
12+
command_approve, command_unapprove, handle_pull_request_edited, handle_push_to_pull_request,
1313
};
1414
use crate::bors::handlers::trybuild::{command_try_build, command_try_cancel, TRY_BRANCH_NAME};
1515
use crate::bors::handlers::workflow::{
@@ -122,6 +122,14 @@ pub async fn handle_bors_repository_event(
122122
.instrument(span.clone())
123123
.await?;
124124
}
125+
BorsRepositoryEvent::PullRequestCommitPushed(payload) => {
126+
let span =
127+
tracing::info_span!("Pull request pushed", repo = payload.repository.to_string());
128+
129+
handle_push_to_pull_request(repo, db, payload)
130+
.instrument(span.clone())
131+
.await?;
132+
}
125133
}
126134
Ok(())
127135
}

src/bors/handlers/review.rs

Lines changed: 105 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ use std::sync::Arc;
22

33
use crate::bors::command::Approver;
44
use crate::bors::event::PullRequestEdited;
5+
use crate::bors::event::PullRequestPushed;
56
use crate::bors::handlers::labels::handle_label_trigger;
67
use crate::bors::Comment;
78
use crate::bors::RepositoryState;
9+
use crate::github::CommitSha;
810
use crate::github::GithubUser;
911
use crate::github::LabelTrigger;
1012
use crate::github::PullRequest;
@@ -67,7 +69,7 @@ pub(super) async fn handle_pull_request_edited(
6769
let pr_model = db
6870
.get_or_create_pull_request(repo_state.repository(), payload.pull_request.number)
6971
.await?;
70-
if pr_model.approved_by.is_none() {
72+
if !pr_model.is_approved() {
7173
return Ok(());
7274
}
7375

@@ -77,6 +79,26 @@ pub(super) async fn handle_pull_request_edited(
7779
notify_of_edited_pr(&repo_state, pr_number, &payload.pull_request.base.name).await
7880
}
7981

82+
pub(super) async fn handle_push_to_pull_request(
83+
repo_state: Arc<RepositoryState>,
84+
db: Arc<PgDbClient>,
85+
payload: PullRequestPushed,
86+
) -> anyhow::Result<()> {
87+
let pr = &payload.pull_request;
88+
let pr_model = db
89+
.get_or_create_pull_request(repo_state.repository(), pr.number)
90+
.await?;
91+
92+
if !pr_model.is_approved() {
93+
return Ok(());
94+
}
95+
96+
let pr_number = pr_model.number;
97+
db.unapprove(repo_state.repository(), pr_number).await?;
98+
handle_label_trigger(&repo_state, pr_number, LabelTrigger::Unapproved).await?;
99+
notify_of_pushed_pr(&repo_state, pr_number, pr.head.sha.clone()).await
100+
}
101+
80102
fn sufficient_approve_permission(repo: Arc<RepositoryState>, author: &GithubUser) -> bool {
81103
repo.permissions
82104
.load()
@@ -176,27 +198,37 @@ PR will need to be re-approved."#,
176198
.await
177199
}
178200

201+
async fn notify_of_pushed_pr(
202+
repo: &RepositoryState,
203+
pr_number: PullRequestNumber,
204+
head_sha: CommitSha,
205+
) -> anyhow::Result<()> {
206+
repo.client
207+
.post_comment(
208+
pr_number,
209+
Comment::new(format!(
210+
r#":warning: A new commit `{}` was pushed to the branch, the
211+
PR will need to be re-approved."#,
212+
head_sha
213+
)),
214+
)
215+
.await
216+
}
217+
179218
#[cfg(test)]
180219
mod tests {
181220
use crate::{
182221
github::PullRequestNumber,
183222
tests::mocks::{
184-
default_pr_number, default_repo_name, BorsBuilder, BorsTester, Permissions,
223+
default_pr_number, default_repo_name, run_test, BorsBuilder, BorsTester, Permissions,
185224
PullRequestChangeEvent, User, World,
186225
},
187226
};
188227

189228
#[sqlx::test]
190229
async fn default_approve(pool: sqlx::PgPool) {
191-
let world = World::default();
192-
world.default_repo().lock().set_config(
193-
r#"
194-
[labels]
195-
approve = ["+approved"]
196-
"#,
197-
);
198230
BorsBuilder::new(pool)
199-
.world(world)
231+
.world(create_world_with_approve_config())
200232
.run_test(|mut tester| async {
201233
tester.post_comment("@bors r+").await?;
202234
assert_eq!(
@@ -221,15 +253,8 @@ approve = ["+approved"]
221253

222254
#[sqlx::test]
223255
async fn approve_on_behalf(pool: sqlx::PgPool) {
224-
let world = World::default();
225-
world.default_repo().lock().set_config(
226-
r#"
227-
[labels]
228-
approve = ["+approved"]
229-
"#,
230-
);
231256
BorsBuilder::new(pool)
232-
.world(world)
257+
.world(create_world_with_approve_config())
233258
.run_test(|mut tester| async {
234259
let approve_user = "user1";
235260
tester
@@ -269,15 +294,8 @@ approve = ["+approved"]
269294

270295
#[sqlx::test]
271296
async fn unapprove(pool: sqlx::PgPool) {
272-
let world = World::default();
273-
world.default_repo().lock().set_config(
274-
r#"
275-
[labels]
276-
approve = ["+approved"]
277-
"#,
278-
);
279297
BorsBuilder::new(pool)
280-
.world(world)
298+
.world(create_world_with_approve_config())
281299
.run_test(|mut tester| async {
282300
tester.post_comment("@bors r+").await?;
283301
assert_eq!(
@@ -307,15 +325,8 @@ approve = ["+approved"]
307325

308326
#[sqlx::test]
309327
async fn unapprove_on_base_edited(pool: sqlx::PgPool) {
310-
let world = World::default();
311-
world.default_repo().lock().set_config(
312-
r#"
313-
[labels]
314-
approve = ["+approved"]
315-
"#,
316-
);
317328
BorsBuilder::new(pool)
318-
.world(world)
329+
.world(create_world_with_approve_config())
319330
.run_test(|mut tester| async {
320331
tester.post_comment("@bors r+").await?;
321332
assert_eq!(
@@ -348,15 +359,8 @@ PR will need to be re-approved."#,
348359

349360
#[sqlx::test]
350361
async fn edit_pr_do_nothing_when_base_not_edited(pool: sqlx::PgPool) {
351-
let world = World::default();
352-
world.default_repo().lock().set_config(
353-
r#"
354-
[labels]
355-
approve = ["+approved"]
356-
"#,
357-
);
358362
BorsBuilder::new(pool)
359-
.world(world)
363+
.world(create_world_with_approve_config())
360364
.run_test(|mut tester| async {
361365
tester.post_comment("@bors r+").await?;
362366
assert_eq!(
@@ -389,31 +393,74 @@ approve = ["+approved"]
389393

390394
#[sqlx::test]
391395
async fn edit_pr_do_nothing_when_not_approved(pool: sqlx::PgPool) {
392-
let world = World::default();
393-
world.default_repo().lock().set_config(
394-
r#"
395-
[labels]
396-
approve = ["+approved"]
397-
"#,
398-
);
396+
run_test(pool, |mut tester| async {
397+
tester
398+
.edit_pull_request(
399+
default_pr_number(),
400+
PullRequestChangeEvent {
401+
from_base_sha: Some("main-sha".to_string()),
402+
},
403+
)
404+
.await?;
405+
406+
// No comment should be posted
407+
Ok(tester)
408+
})
409+
.await;
410+
}
411+
412+
#[sqlx::test]
413+
async fn unapprove_on_push(pool: sqlx::PgPool) {
399414
BorsBuilder::new(pool)
400-
.world(world)
415+
.world(create_world_with_approve_config())
401416
.run_test(|mut tester| async {
402-
tester
403-
.edit_pull_request(
417+
tester.post_comment("@bors r+").await?;
418+
assert_eq!(
419+
tester.get_comment().await?,
420+
format!(
421+
"Commit pr-{}-sha has been approved by `{}`",
404422
default_pr_number(),
405-
PullRequestChangeEvent {
406-
from_base_sha: Some("main-sha".to_string()),
407-
},
408-
)
409-
.await?;
423+
User::default_user().name
424+
),
425+
);
426+
tester.push_to_pull_request(default_pr_number()).await?;
410427

411-
// No comment should be posted
428+
assert_eq!(
429+
tester.get_comment().await?,
430+
format!(
431+
r#":warning: A new commit `pr-{}-sha` was pushed to the branch, the
432+
PR will need to be re-approved."#,
433+
default_pr_number()
434+
)
435+
);
436+
check_pr_unapproved(&tester, default_pr_number().into()).await;
412437
Ok(tester)
413438
})
414439
.await;
415440
}
416441

442+
#[sqlx::test]
443+
async fn push_to_pr_do_nothing_when_not_approved(pool: sqlx::PgPool) {
444+
run_test(pool, |mut tester| async {
445+
tester.push_to_pull_request(default_pr_number()).await?;
446+
447+
// No comment should be posted
448+
Ok(tester)
449+
})
450+
.await;
451+
}
452+
453+
fn create_world_with_approve_config() -> World {
454+
let world = World::default();
455+
world.default_repo().lock().set_config(
456+
r#"
457+
[labels]
458+
approve = ["+approved"]
459+
"#,
460+
);
461+
world
462+
}
463+
417464
async fn check_pr_approved_by(
418465
tester: &BorsTester,
419466
pr_number: PullRequestNumber,

src/database/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,12 @@ pub struct PullRequestModel {
121121
pub created_at: DateTime<Utc>,
122122
}
123123

124+
impl PullRequestModel {
125+
pub fn is_approved(&self) -> bool {
126+
self.approved_by.is_some()
127+
}
128+
}
129+
124130
/// Describes whether a workflow is a Github Actions workflow or if it's a job from some external
125131
/// CI.
126132
#[derive(Debug, PartialEq, sqlx::Type)]

0 commit comments

Comments
 (0)