Skip to content

Commit ea094b6

Browse files
authored
Merge pull request #247 from Sakib25800/feat/async-merge-check
Implement refresh mechanism for unknown mergeable PRs
2 parents b1981d5 + 9c14eda commit ea094b6

File tree

7 files changed

+273
-21
lines changed

7 files changed

+273
-21
lines changed

.sqlx/query-07ad0343b05020b1df6f21b8863ad848f096688a8e41e74f9481e1c27b381bb9.json

+124
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/bors/handlers/mod.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ pub async fn handle_bors_global_event(
219219
ctx: Arc<BorsContext>,
220220
gh_client: &Octocrab,
221221
team_api_client: &TeamApiClient,
222+
mergeable_queue_tx: MergeableQueueSender,
222223
) -> anyhow::Result<()> {
223224
let db = Arc::clone(&ctx.db);
224225
match event {
@@ -234,9 +235,10 @@ pub async fn handle_bors_global_event(
234235
ctx.repositories.read().unwrap().values().cloned().collect();
235236
futures::future::join_all(repos.into_iter().map(|repo| {
236237
let repo = Arc::clone(&repo);
238+
let mergeable_queue_tx = mergeable_queue_tx.clone();
237239
async {
238240
let subspan = tracing::info_span!("Repo", repo = repo.repository().to_string());
239-
refresh_repository(repo, Arc::clone(&db), team_api_client)
241+
refresh_repository(repo, Arc::clone(&db), team_api_client, mergeable_queue_tx)
240242
.instrument(subspan)
241243
.await
242244
}

src/bors/handlers/pr_events.rs

-1
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,6 @@ mod tests {
440440
.await;
441441
}
442442

443-
#[tracing_test::traced_test]
444443
#[sqlx::test]
445444
async fn open_and_merge_pr(pool: sqlx::PgPool) {
446445
run_test(pool, |mut tester| async {

src/bors/handlers/refresh.rs

+82-6
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,32 @@ use std::time::Duration;
33

44
use anyhow::Context;
55
use chrono::{DateTime, Utc};
6+
use futures::FutureExt;
7+
use futures::future::join_all;
68

79
use crate::bors::Comment;
810
use crate::bors::RepositoryState;
911
use crate::bors::handlers::trybuild::cancel_build_workflows;
12+
use crate::bors::mergeable_queue::MergeableQueueSender;
1013
use crate::database::BuildStatus;
1114
use crate::{PgDbClient, TeamApiClient};
1215

1316
pub async fn refresh_repository(
1417
repo: Arc<RepositoryState>,
1518
db: Arc<PgDbClient>,
1619
team_api_client: &TeamApiClient,
20+
mergeable_queue_tx: MergeableQueueSender,
1721
) -> anyhow::Result<()> {
1822
let repo = repo.as_ref();
19-
if let (Ok(_), _, Ok(_)) = tokio::join!(
20-
cancel_timed_out_builds(repo, db.as_ref()),
21-
reload_permission(repo, team_api_client),
22-
reload_config(repo)
23-
) {
23+
let results = join_all([
24+
cancel_timed_out_builds(repo, db.as_ref()).boxed(),
25+
reload_permission(repo, team_api_client).boxed(),
26+
reload_config(repo).boxed(),
27+
reload_unknown_mergeable_prs(repo, db.as_ref(), mergeable_queue_tx).boxed(),
28+
])
29+
.await;
30+
31+
if results.iter().all(|result| result.is_ok()) {
2432
Ok(())
2533
} else {
2634
tracing::error!("Failed to refresh repository");
@@ -79,6 +87,27 @@ async fn reload_permission(
7987
Ok(())
8088
}
8189

90+
async fn reload_unknown_mergeable_prs(
91+
repo: &RepositoryState,
92+
db: &PgDbClient,
93+
mergeable_queue: MergeableQueueSender,
94+
) -> anyhow::Result<()> {
95+
let prs = db
96+
.get_prs_with_unknown_mergeable_state(repo.repository())
97+
.await?;
98+
99+
tracing::info!(
100+
"Refreshing {} PR(s) with unknown mergeable state",
101+
prs.len()
102+
);
103+
104+
for pr in prs {
105+
mergeable_queue.enqueue(repo.repository().clone(), pr.number);
106+
}
107+
108+
Ok(())
109+
}
110+
82111
async fn reload_config(repo: &RepositoryState) -> anyhow::Result<()> {
83112
let config = repo.client.load_config().await?;
84113
repo.config.store(Arc::new(config));
@@ -109,8 +138,9 @@ fn elapsed_time(date: DateTime<Utc>) -> Duration {
109138
mod tests {
110139
use crate::bors::handlers::WAIT_FOR_WORKFLOW_STARTED;
111140
use crate::bors::handlers::refresh::MOCK_TIME;
141+
use crate::database::{MergeableState, OctocrabMergeableState};
112142
use crate::tests::mocks::{
113-
BorsBuilder, GitHubState, WorkflowEvent, default_repo_name, run_test,
143+
BorsBuilder, GitHubState, WorkflowEvent, default_pr_number, default_repo_name, run_test,
114144
};
115145
use chrono::Utc;
116146
use std::future::Future;
@@ -133,6 +163,7 @@ timeout = 3600
133163
"#,
134164
)
135165
}
166+
136167
#[sqlx::test]
137168
async fn refresh_do_nothing_before_timeout(pool: sqlx::PgPool) {
138169
BorsBuilder::new(pool)
@@ -207,6 +238,51 @@ timeout = 3600
207238
gh.check_cancelled_workflows(default_repo_name(), &[1]);
208239
}
209240

241+
#[sqlx::test]
242+
async fn refresh_enqueues_unknown_mergeable_prs(pool: sqlx::PgPool) {
243+
run_test(pool, |mut tester| async {
244+
tester
245+
.edit_pr(default_repo_name(), default_pr_number(), |pr| {
246+
pr.mergeable_state = OctocrabMergeableState::Unknown
247+
})
248+
.await?;
249+
tester
250+
.wait_for_default_pr(|pr| pr.mergeable_state == MergeableState::Unknown)
251+
.await?;
252+
tester
253+
.default_repo()
254+
.lock()
255+
.get_pr_mut(default_pr_number())
256+
.mergeable_state = OctocrabMergeableState::Dirty;
257+
tester.refresh().await;
258+
tester
259+
.wait_for_default_pr(|pr| pr.mergeable_state == MergeableState::HasConflicts)
260+
.await?;
261+
Ok(tester)
262+
})
263+
.await;
264+
}
265+
266+
#[sqlx::test]
267+
async fn refresh_enqueues_no_known_mergeable_prs(pool: sqlx::PgPool) {
268+
run_test(pool, |mut tester| async {
269+
tester
270+
.edit_pr(default_repo_name(), default_pr_number(), |pr| {
271+
pr.mergeable_state = OctocrabMergeableState::Clean
272+
})
273+
.await?;
274+
tester
275+
.wait_for_default_pr(|pr| pr.mergeable_state == MergeableState::Mergeable)
276+
.await?;
277+
tester.refresh().await;
278+
tester
279+
.wait_for_default_pr(|pr| pr.mergeable_state == MergeableState::Mergeable)
280+
.await?;
281+
Ok(tester)
282+
})
283+
.await;
284+
}
285+
210286
async fn with_mocked_time<Fut: Future<Output = ()>>(in_future: Duration, future: Fut) {
211287
// It is important to use this function only with a single threaded runtime,
212288
// otherwise the `MOCK_TIME` variable might get mixed up between different threads.

src/database/client.rs

+13-5
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@ use crate::github::{CommitSha, GithubRepoName};
1111
use super::operations::{
1212
approve_pull_request, create_build, create_pull_request, create_workflow,
1313
delegate_pull_request, find_build, find_pr_by_build,
14-
get_nonclosed_pull_requests_by_base_branch, get_pull_request, get_repository,
15-
get_running_builds, get_workflow_urls_for_build, get_workflows_for_build, set_pr_priority,
16-
set_pr_rollup, set_pr_status, unapprove_pull_request, undelegate_pull_request,
17-
update_build_status, update_mergeable_states_by_base_branch, update_pr_build_id,
18-
update_pr_mergeable_state, update_workflow_status, upsert_pull_request, upsert_repository,
14+
get_nonclosed_pull_requests_by_base_branch, get_prs_with_unknown_mergeable_state,
15+
get_pull_request, get_repository, get_running_builds, get_workflow_urls_for_build,
16+
get_workflows_for_build, set_pr_priority, set_pr_rollup, set_pr_status, unapprove_pull_request,
17+
undelegate_pull_request, update_build_status, update_mergeable_states_by_base_branch,
18+
update_pr_build_id, update_pr_mergeable_state, update_workflow_status, upsert_pull_request,
19+
upsert_repository,
1920
};
2021
use super::{ApprovalInfo, DelegatedPermission, MergeableState, RunId};
2122

@@ -121,6 +122,13 @@ impl PgDbClient {
121122
get_nonclosed_pull_requests_by_base_branch(&self.pool, repo, base_branch).await
122123
}
123124

125+
pub async fn get_prs_with_unknown_mergeable_state(
126+
&self,
127+
repo: &GithubRepoName,
128+
) -> anyhow::Result<Vec<PullRequestModel>> {
129+
get_prs_with_unknown_mergeable_state(&self.pool, repo).await
130+
}
131+
124132
pub async fn create_pull_request(
125133
&self,
126134
repo: &GithubRepoName,

src/database/operations.rs

+40
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,46 @@ pub(crate) async fn update_pr_mergeable_state(
225225
.await
226226
}
227227

228+
pub(crate) async fn get_prs_with_unknown_mergeable_state(
229+
executor: impl PgExecutor<'_>,
230+
repo: &GithubRepoName,
231+
) -> anyhow::Result<Vec<PullRequestModel>> {
232+
measure_db_query("get_prs_with_unknown_mergeable_state", || async {
233+
let records = sqlx::query_as!(
234+
PullRequestModel,
235+
r#"
236+
SELECT
237+
pr.id,
238+
pr.repository as "repository: GithubRepoName",
239+
pr.number as "number!: i64",
240+
(
241+
pr.approved_by,
242+
pr.approved_sha
243+
) AS "approval_status!: ApprovalStatus",
244+
pr.status as "pr_status: PullRequestStatus",
245+
pr.priority,
246+
pr.rollup as "rollup: RollupMode",
247+
pr.delegated_permission as "delegated_permission: DelegatedPermission",
248+
pr.base_branch,
249+
pr.mergeable_state as "mergeable_state: MergeableState",
250+
pr.created_at as "created_at: DateTime<Utc>",
251+
build AS "try_build: BuildModel"
252+
FROM pull_request as pr
253+
LEFT JOIN build ON pr.build_id = build.id
254+
WHERE pr.repository = $1
255+
AND pr.mergeable_state = 'unknown'
256+
AND pr.status IN ('open', 'draft')
257+
"#,
258+
repo as &GithubRepoName
259+
)
260+
.fetch_all(executor)
261+
.await?;
262+
263+
Ok(records)
264+
})
265+
.await
266+
}
267+
228268
pub(crate) async fn update_mergeable_states_by_base_branch(
229269
executor: impl PgExecutor<'_>,
230270
repo: &GithubRepoName,

0 commit comments

Comments
 (0)