@@ -10,6 +10,7 @@ use axum::http::StatusCode;
1010use axum:: response:: { IntoResponse , Redirect , Response } ;
1111use futures:: StreamExt ;
1212use rand:: { Rng , distr:: Alphanumeric } ;
13+ use std:: collections:: HashSet ;
1314use std:: sync:: Arc ;
1415use tracing:: Instrument ;
1516
@@ -151,16 +152,23 @@ async fn create_rollup(
151152 let OAuthRollupState {
152153 repo_name,
153154 repo_owner,
154- pr_nums,
155+ mut pr_nums,
155156 } = rollup_state;
156157
157158 let username = user_client. username ;
158159
159160 tracing:: info!( "User {username} is creating a rollup with PRs: {pr_nums:?}" ) ;
160161
162+ // Filter out duplicates
163+ let mut pr_set = HashSet :: new ( ) ;
164+ pr_nums. retain ( |& pr| pr_set. insert ( pr) ) ;
165+
161166 if pr_nums. len ( ) as u64 > ROLLUP_PR_LIMIT {
162167 return Err ( RollupError :: TooManyPullRequests ) ;
163168 }
169+ if pr_nums. is_empty ( ) {
170+ return Err ( RollupError :: NoPullRequestsSelected ) ;
171+ }
164172
165173 // Ensure user has a fork
166174 match user_client. client . get_repo ( ) . await {
@@ -198,10 +206,6 @@ async fn create_rollup(
198206 }
199207 }
200208
201- if rollup_prs. is_empty ( ) {
202- return Err ( RollupError :: NoPullRequestsSelected ) ;
203- }
204-
205209 // Sort PRs by priority and then PR number
206210 // We want to try to merge PRs with higher priority first, so that in case of conflicts they
207211 // get in, rather than being left out.
@@ -553,6 +557,31 @@ mod tests {
553557 " ) ;
554558 }
555559
560+ #[ sqlx:: test]
561+ async fn rollup_duplicates ( pool : sqlx:: PgPool ) {
562+ let gh = run_test ( ( pool, rollup_state ( ) ) , async |ctx : & mut BorsTester | {
563+ let pr2 = ctx. open_pr ( default_repo_name ( ) , |_| { } ) . await ?;
564+ let pr3 = ctx. open_pr ( default_repo_name ( ) , |_| { } ) . await ?;
565+ ctx. approve ( pr2. id ( ) ) . await ?;
566+ ctx. approve ( pr3. id ( ) ) . await ?;
567+
568+ make_rollup ( ctx, & [ & pr3, & pr2, & pr3, & pr2] )
569+ . await ?
570+ . assert_status ( StatusCode :: TEMPORARY_REDIRECT ) ;
571+ Ok ( ( ) )
572+ } )
573+ . await ;
574+ let repo = gh. get_repo ( ( ) ) ;
575+ insta:: assert_snapshot!( repo. lock( ) . get_pr( 4 ) . description, @r"
576+ Successful merges:
577+
578+ - #2 (Title of PR 2)
579+ - #3 (Title of PR 3)
580+
581+ r? @ghost
582+ " ) ;
583+ }
584+
556585 async fn make_rollup (
557586 ctx : & mut BorsTester ,
558587 prs : & [ & PullRequest ] ,
0 commit comments