@@ -9,6 +9,7 @@ use crate::sync::Config;
99use crate :: sync:: github:: api:: {
1010 GithubRead , Login , PushAllowanceActor , RepoPermission , RepoSettings , Ruleset ,
1111} ;
12+ use anyhow:: Context as _;
1213use futures_util:: StreamExt ;
1314use log:: debug;
1415use rust_team_data:: v1:: { Bot , BranchProtectionMode , MergeBot , ProtectionTarget } ;
@@ -447,6 +448,78 @@ impl SyncGitHub {
447448 self . config . enable_rulesets_repos . contains ( & repo_full_name)
448449 }
449450
451+ async fn construct_ruleset (
452+ & self ,
453+ expected_repo : & rust_team_data:: v1:: Repo ,
454+ branch_protection : & rust_team_data:: v1:: BranchProtection ,
455+ ) -> anyhow:: Result < api:: Ruleset > {
456+ let bypass_actors = self . bypass_actors ( expected_repo, branch_protection) . await ?;
457+
458+ Ok ( construct_ruleset ( branch_protection, bypass_actors) )
459+ }
460+
461+ async fn bypass_actors (
462+ & self ,
463+ expected_repo : & rust_team_data:: v1:: Repo ,
464+ branch_protection : & rust_team_data:: v1:: BranchProtection ,
465+ ) -> Result < Vec < api:: RulesetBypassActor > , anyhow:: Error > {
466+ use api:: { RulesetActorType , RulesetBypassActor , RulesetBypassMode } ;
467+
468+ let mut bypass_actors = Vec :: new ( ) ;
469+ let allowed_teams = self
470+ . allowed_merge_teams ( expected_repo, branch_protection)
471+ . await ?;
472+ bypass_actors. extend ( allowed_teams) ;
473+ let allowed_apps = branch_protection
474+ . allowed_merge_apps
475+ . iter ( )
476+ . filter_map ( |app| {
477+ app. app_id ( ) . map ( |app_id| RulesetBypassActor {
478+ actor_id : app_id,
479+ actor_type : RulesetActorType :: Integration ,
480+ bypass_mode : RulesetBypassMode :: Always ,
481+ } )
482+ } ) ;
483+ bypass_actors. extend ( allowed_apps) ;
484+ Ok ( bypass_actors)
485+ }
486+
487+ async fn allowed_merge_teams (
488+ & self ,
489+ expected_repo : & rust_team_data:: v1:: Repo ,
490+ branch_protection : & rust_team_data:: v1:: BranchProtection ,
491+ ) -> Result < Vec < api:: RulesetBypassActor > , anyhow:: Error > {
492+ use api:: { RulesetActorType , RulesetBypassActor , RulesetBypassMode } ;
493+
494+ let mut allowed = vec ! [ ] ;
495+
496+ for team_name in & branch_protection. allowed_merge_teams {
497+ let github_team = self
498+ . github
499+ . team ( & expected_repo. org , team_name)
500+ . await ?
501+ . with_context ( || {
502+ format ! (
503+ "failed to find GitHub team '{team_name}' in org '{}' for repo '{}/{}'" ,
504+ expected_repo. org, expected_repo. org, expected_repo. name
505+ )
506+ } ) ?;
507+ let team_id = github_team. id . with_context ( || {
508+ format ! (
509+ "GitHub team '{team_name}' in org '{}' is missing an ID" ,
510+ expected_repo. org
511+ )
512+ } ) ?;
513+
514+ allowed. push ( RulesetBypassActor {
515+ actor_id : team_id as i64 ,
516+ actor_type : RulesetActorType :: Team ,
517+ bypass_mode : RulesetBypassMode :: Always ,
518+ } ) ;
519+ }
520+ Ok ( allowed)
521+ }
522+
450523 async fn diff_repo (
451524 & self ,
452525 expected_repo : & rust_team_data:: v1:: Repo ,
@@ -481,7 +554,9 @@ impl SyncGitHub {
481554 let use_rulesets = self . should_use_rulesets ( expected_repo) ;
482555 if use_rulesets {
483556 for branch_protection in & expected_repo. branch_protections {
484- let ruleset = construct_ruleset ( branch_protection) ;
557+ let ruleset = self
558+ . construct_ruleset ( expected_repo, branch_protection)
559+ . await ?;
485560 rulesets. push ( ruleset) ;
486561 }
487562 }
@@ -799,7 +874,9 @@ impl SyncGitHub {
799874
800875 // Process each branch protection as a potential ruleset
801876 for branch_protection in & expected_repo. branch_protections {
802- let expected_ruleset = construct_ruleset ( branch_protection) ;
877+ let expected_ruleset = self
878+ . construct_ruleset ( expected_repo, branch_protection)
879+ . await ?;
803880
804881 if let Some ( actual_ruleset) = rulesets_by_name. remove ( & expected_ruleset. name ) {
805882 let Ruleset {
@@ -1179,11 +1256,12 @@ fn github_int(value: u32) -> i32 {
11791256 i32:: try_from ( value) . unwrap_or_else ( |_| panic ! ( "Value {value} exceeds GitHub's Int range" ) )
11801257}
11811258
1182- pub fn construct_ruleset ( branch_protection : & rust_team_data:: v1:: BranchProtection ) -> api:: Ruleset {
1259+ pub fn construct_ruleset (
1260+ branch_protection : & rust_team_data:: v1:: BranchProtection ,
1261+ bypass_actors : Vec < api:: RulesetBypassActor > ,
1262+ ) -> api:: Ruleset {
11831263 use api:: * ;
11841264
1185- let branch_protection_mode = get_branch_protection_mode ( branch_protection) ;
1186-
11871265 // Use a BTreeSet to ensure a consistent order. This avoids unnecessary diffs when the order of rules changes,
11881266 // since GitHub does not guarantee any specific order for rules.
11891267 let mut rules: BTreeSet < RulesetRule > = BTreeSet :: new ( ) ;
@@ -1214,22 +1292,22 @@ pub fn construct_ruleset(branch_protection: &rust_team_data::v1::BranchProtectio
12141292 // Add pull request rule if PRs are required
12151293 if let BranchProtectionMode :: PrRequired {
12161294 required_approvals, ..
1217- } = & branch_protection_mode
1295+ } = branch_protection . mode
12181296 {
12191297 rules. insert ( RulesetRule :: PullRequest {
12201298 parameters : PullRequestParameters {
12211299 dismiss_stale_reviews_on_push : branch_protection. dismiss_stale_review ,
12221300 require_code_owner_review : REQUIRE_CODE_OWNER_REVIEW_DEFAULT ,
12231301 require_last_push_approval : REQUIRE_LAST_PUSH_APPROVAL_DEFAULT ,
1224- required_approving_review_count : github_int ( * required_approvals) ,
1302+ required_approving_review_count : github_int ( required_approvals) ,
12251303 required_review_thread_resolution : branch_protection
12261304 . require_conversation_resolution ,
12271305 } ,
12281306 } ) ;
12291307 }
12301308
12311309 // Add required status checks if any
1232- if let BranchProtectionMode :: PrRequired { ci_checks, .. } = & branch_protection_mode
1310+ if let BranchProtectionMode :: PrRequired { ci_checks, .. } = & branch_protection . mode
12331311 && !ci_checks. is_empty ( )
12341312 {
12351313 let mut checks = ci_checks. clone ( ) ;
@@ -1271,19 +1349,6 @@ pub fn construct_ruleset(branch_protection: &rust_team_data::v1::BranchProtectio
12711349 rules. insert ( RulesetRule :: MergeQueue { parameters } ) ;
12721350 }
12731351
1274- // Build bypass actors from allowed merge apps
1275- let bypass_actors: Vec < RulesetBypassActor > = branch_protection
1276- . allowed_merge_apps
1277- . iter ( )
1278- . filter_map ( |app| {
1279- app. app_id ( ) . map ( |app_id| RulesetBypassActor {
1280- actor_id : app_id,
1281- actor_type : RulesetActorType :: Integration ,
1282- bypass_mode : RulesetBypassMode :: Always ,
1283- } )
1284- } )
1285- . collect ( ) ;
1286-
12871352 api:: Ruleset {
12881353 id : None ,
12891354 name : branch_protection
0 commit comments