Skip to content

Commit 35ed9d3

Browse files
feat: unapprove on edit
1 parent 83e7c77 commit 35ed9d3

File tree

4 files changed

+105
-5
lines changed

4 files changed

+105
-5
lines changed

src/bors/event.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
use crate::database::{WorkflowStatus, WorkflowType};
2-
use crate::github::{CommitSha, GithubRepoName, GithubUser, PullRequestNumber};
2+
use crate::github::{CommitSha, GithubRepoName, GithubUser, PullRequest, PullRequestNumber};
33
use octocrab::models::RunId;
44

55
#[derive(Debug)]
66
pub enum BorsRepositoryEvent {
77
/// A comment was posted on a pull request.
88
Comment(PullRequestComment),
9+
/// When the pull request is edited by its author
10+
PullRequestEdited(PullRequestEdited),
911
/// A workflow run on Github Actions or a check run from external CI system has been started.
1012
WorkflowStarted(WorkflowStarted),
1113
/// A workflow run on Github Actions or a check run from external CI system has been completed.
@@ -22,6 +24,7 @@ impl BorsRepositoryEvent {
2224
BorsRepositoryEvent::WorkflowStarted(workflow) => &workflow.repository,
2325
BorsRepositoryEvent::WorkflowCompleted(workflow) => &workflow.repository,
2426
BorsRepositoryEvent::CheckSuiteCompleted(payload) => &payload.repository,
27+
BorsRepositoryEvent::PullRequestEdited(payload) => &payload.repository,
2528
}
2629
}
2730
}
@@ -50,6 +53,13 @@ pub struct PullRequestComment {
5053
pub text: String,
5154
}
5255

56+
#[derive(Debug)]
57+
pub struct PullRequestEdited {
58+
pub repository: GithubRepoName,
59+
pub pull_request: PullRequest,
60+
pub from_base_sha: Option<CommitSha>,
61+
}
62+
5363
#[derive(Debug)]
5464
pub struct WorkflowStarted {
5565
pub repository: GithubRepoName,

src/bors/handlers/mod.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ use crate::bors::event::{BorsGlobalEvent, BorsRepositoryEvent, PullRequestCommen
88
use crate::bors::handlers::help::command_help;
99
use crate::bors::handlers::ping::command_ping;
1010
use crate::bors::handlers::refresh::refresh_repository;
11-
use crate::bors::handlers::review::{command_approve, command_unapprove};
11+
use crate::bors::handlers::review::{
12+
command_approve, command_unapprove, handle_pull_request_edited,
13+
};
1214
use crate::bors::handlers::trybuild::{command_try_build, command_try_cancel, TRY_BRANCH_NAME};
1315
use crate::bors::handlers::workflow::{
1416
handle_check_suite_completed, handle_workflow_completed, handle_workflow_started,
@@ -112,6 +114,14 @@ pub async fn handle_bors_repository_event(
112114
.instrument(span.clone())
113115
.await?;
114116
}
117+
BorsRepositoryEvent::PullRequestEdited(payload) => {
118+
let span =
119+
tracing::info_span!("Pull request edited", repo = payload.repository.to_string());
120+
121+
handle_pull_request_edited(repo, db, payload)
122+
.instrument(span.clone())
123+
.await?;
124+
}
115125
}
116126
Ok(())
117127
}

src/bors/handlers/review.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
use std::sync::Arc;
22

33
use crate::bors::command::Approver;
4+
use crate::bors::event::PullRequestEdited;
45
use crate::bors::handlers::labels::handle_label_trigger;
56
use crate::bors::Comment;
67
use crate::bors::RepositoryState;
8+
use crate::github::CommitSha;
79
use crate::github::GithubUser;
810
use crate::github::LabelTrigger;
911
use crate::github::PullRequest;
12+
use crate::github::PullRequestNumber;
1013
use crate::permissions::PermissionType;
1114
use crate::PgDbClient;
1215

@@ -52,6 +55,21 @@ pub(super) async fn command_unapprove(
5255
notify_of_unapproval(&repo_state, pr).await
5356
}
5457

58+
pub(super) async fn handle_pull_request_edited(
59+
repo_state: Arc<RepositoryState>,
60+
db: Arc<PgDbClient>,
61+
payload: PullRequestEdited,
62+
) -> anyhow::Result<()> {
63+
// If the base branch has changed, unapprove the PR
64+
let Some(_) = payload.from_base_sha else {
65+
return Ok(());
66+
};
67+
let pr_number = payload.pull_request.number;
68+
db.unapprove(repo_state.repository(), pr_number).await?;
69+
handle_label_trigger(&repo_state, pr_number, LabelTrigger::Unapproved).await?;
70+
notify_of_edited_pr(&repo_state, pr_number, payload.pull_request.head.sha).await
71+
}
72+
5573
fn sufficient_approve_permission(repo: Arc<RepositoryState>, author: &GithubUser) -> bool {
5674
repo.permissions
5775
.load()
@@ -135,6 +153,23 @@ async fn notify_of_unapproval(repo: &RepositoryState, pr: &PullRequest) -> anyho
135153
.await
136154
}
137155

156+
async fn notify_of_edited_pr(
157+
repo: &RepositoryState,
158+
pr_number: PullRequestNumber,
159+
head_sha: CommitSha,
160+
) -> anyhow::Result<()> {
161+
repo.client
162+
.post_comment(
163+
pr_number,
164+
Comment::new(format!(
165+
r#":warning: The base branch changed to `{}`, and the
166+
PR will need to be re-approved."#,
167+
head_sha
168+
)),
169+
)
170+
.await
171+
}
172+
138173
#[cfg(test)]
139174
mod tests {
140175
use crate::{
@@ -226,7 +261,6 @@ approve = ["+approved"]
226261
}
227262

228263
#[sqlx::test]
229-
#[tracing_test::traced_test]
230264
async fn unapprove(pool: sqlx::PgPool) {
231265
let world = World::default();
232266
world.default_repo().lock().set_config(

src/github/webhook.rs

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ use axum::http::{HeaderMap, HeaderValue, StatusCode};
88
use axum::{async_trait, RequestExt};
99
use hmac::{Hmac, Mac};
1010
use octocrab::models::events::payload::{
11-
IssueCommentEventAction, IssueCommentEventPayload, PullRequestReviewCommentEventAction,
11+
IssueCommentEventAction, IssueCommentEventPayload, PullRequestEventAction,
12+
PullRequestEventChangesFrom, PullRequestReviewCommentEventAction,
1213
PullRequestReviewCommentEventPayload,
1314
};
1415
use octocrab::models::pulls::{PullRequest, Review};
@@ -18,7 +19,7 @@ use sha2::Sha256;
1819

1920
use crate::bors::event::{
2021
BorsEvent, BorsGlobalEvent, BorsRepositoryEvent, CheckSuiteCompleted, PullRequestComment,
21-
WorkflowCompleted, WorkflowStarted,
22+
PullRequestEdited, WorkflowCompleted, WorkflowStarted,
2223
};
2324
use crate::database::{WorkflowStatus, WorkflowType};
2425
use crate::github::server::ServerStateRef;
@@ -90,6 +91,26 @@ pub struct WebhookPullRequestReviewEvent<'a> {
9091
sender: Author,
9192
}
9293

94+
/// Similar to PullRequestEvent from octocrab, but changes field also includes base sha.
95+
/// https://docs.github.com/en/webhooks/webhook-events-and-payloads#pull_request
96+
#[derive(Debug, serde::Deserialize)]
97+
struct WebhookPullRequestEvent {
98+
action: PullRequestEventAction,
99+
pull_request: PullRequest,
100+
changes: Option<WebhookPullRequestChanges>,
101+
repository: Repository,
102+
}
103+
104+
#[derive(Debug, serde::Deserialize)]
105+
struct WebhookPullRequestChanges {
106+
base: Option<WebhookPullRequestBaseChanges>,
107+
}
108+
109+
#[derive(Debug, serde::Deserialize)]
110+
struct WebhookPullRequestBaseChanges {
111+
sha: Option<PullRequestEventChangesFrom>,
112+
}
113+
93114
/// axum extractor for GitHub webhook events.
94115
#[derive(Debug)]
95116
pub struct GitHubWebhook(pub BorsEvent);
@@ -149,6 +170,7 @@ fn parse_webhook_event(request: Parts, body: &[u8]) -> anyhow::Result<Option<Bor
149170

150171
match event_type.as_bytes() {
151172
b"issue_comment" => parse_issue_comment_event(body),
173+
b"pull_request" => parse_pull_request_events(body),
152174
b"pull_request_review" => parse_pull_request_review_events(body),
153175
b"pull_request_review_comment" => parse_pull_request_review_comment_events(body),
154176
b"installation_repositories" | b"installation" => Ok(Some(BorsEvent::Global(
@@ -179,6 +201,30 @@ fn parse_issue_comment_event(body: &[u8]) -> anyhow::Result<Option<BorsEvent>> {
179201
}
180202
}
181203

204+
fn parse_pull_request_events(body: &[u8]) -> anyhow::Result<Option<BorsEvent>> {
205+
let payload: WebhookPullRequestEvent = serde_json::from_slice(body)?;
206+
let repository_name = parse_repository_name(&payload.repository)?;
207+
if payload.action == PullRequestEventAction::Edited {
208+
let Some(changes) = payload.changes else {
209+
return Err(anyhow::anyhow!(
210+
"Edited pull request event should have `changes` field"
211+
));
212+
};
213+
Ok(Some(BorsEvent::Repository(
214+
BorsRepositoryEvent::PullRequestEdited(PullRequestEdited {
215+
repository: repository_name,
216+
pull_request: payload.pull_request.into(),
217+
from_base_sha: changes
218+
.base
219+
.and_then(|base| base.sha)
220+
.map(|sha| CommitSha(sha.from)),
221+
}),
222+
)))
223+
} else {
224+
Ok(None)
225+
}
226+
}
227+
182228
fn parse_pull_request_review_events(body: &[u8]) -> anyhow::Result<Option<BorsEvent>> {
183229
let payload: WebhookPullRequestReviewEvent = serde_json::from_slice(body)?;
184230
if payload.action == "submitted" {

0 commit comments

Comments
 (0)