@@ -5,6 +5,7 @@ mod tests;
55use self :: api:: { BranchProtectionOp , TeamPrivacy , TeamRole } ;
66use crate :: Config ;
77use crate :: github:: api:: { GithubRead , Login , PushAllowanceActor , RepoPermission , RepoSettings } ;
8+ use anyhow:: Context ;
89use log:: debug;
910use rust_team_data:: v1:: { Bot , BranchProtectionMode , MergeBot } ;
1011use std:: collections:: { BTreeMap , BTreeSet , HashMap , HashSet } ;
@@ -981,6 +982,7 @@ pub(crate) fn construct_bypass_actors(
981982) -> anyhow:: Result < Vec < api:: RulesetBypassActor > > {
982983 let mut bypass_actors = Vec :: new ( ) ;
983984
985+ // Resolve team bypass actors
984986 for team_name in & branch_protection. allowed_merge_teams {
985987 match github_write. resolve_team_database_id ( org, team_name) {
986988 Ok ( Some ( actor_id) ) => {
@@ -992,40 +994,39 @@ pub(crate) fn construct_bypass_actors(
992994 }
993995 Ok ( None ) => {
994996 debug ! (
995- "Warning: Could not resolve database ID for team '{}/{}'. \
996- The team may not exist in the organization.",
997+ "Team '{}/{}' not found; skipping bypass actor . \
998+ Verify the team exists in the organization.",
997999 org, team_name
9981000 ) ;
9991001 }
10001002 Err ( e) => {
1001- anyhow:: bail!(
1002- "Failed to resolve database ID for team '{}/{}': {}" ,
1003- org,
1004- team_name,
1005- e
1006- ) ;
1003+ return Err ( e) . context ( format ! (
1004+ "Failed to resolve team '{}/{}' for bypass actor" ,
1005+ org, team_name
1006+ ) ) ;
10071007 }
10081008 }
10091009 }
10101010
1011- // Note: GitHub API has limited support for individual user bypass actors.
1012- // OrganizationAdmin with actor_id=1 grants bypass to all org admins.
1013- // For individual users, they must be added to a team and use allowed_merge_teams.
1011+ // Add OrganizationAdmin bypass actor if allowed_merge_users is specified.
1012+ // Note: GitHub API limitation - cannot add individual users as bypass actors.
1013+ // OrganizationAdmin with actor_id=1 grants bypass to ALL organization admins.
1014+ // For granular per-user control, use allowed_merge_teams with a team containing specific users.
10141015 if !branch_protection. allowed_merge_users . is_empty ( ) {
10151016 debug ! (
1016- "Warning: Individual user bypass actors ( allowed_merge_users) have limited API support . \
1017- Users: {:?}. Only organization admins can bypass . \
1018- For granular control, add users to a team and use allowed_merge_teams instead.",
1017+ "Using OrganizationAdmin bypass actor for allowed_merge_users: {:?} . \
1018+ Note: This grants bypass to ALL org admins, not just specified users . \
1019+ For per-user control, use allowed_merge_teams instead.",
10191020 branch_protection. allowed_merge_users
10201021 ) ;
1021- // Add OrganizationAdmin bypass actor (applies to all org admins)
10221022 bypass_actors. push ( api:: RulesetBypassActor {
10231023 actor_id : Some ( api:: ActorId :: Id ( 1 ) ) ,
10241024 actor_type : api:: RulesetActorType :: OrganizationAdmin ,
10251025 bypass_mode : Some ( api:: RulesetBypassMode :: Always ) ,
10261026 } ) ;
10271027 }
10281028
1029+ // Resolve bot integration bypass actors
10291030 for merge_bot in & branch_protection. merge_bots {
10301031 let user_login = match merge_bot {
10311032 MergeBot :: Homu => "bors" ,
@@ -1041,17 +1042,16 @@ pub(crate) fn construct_bypass_actors(
10411042 }
10421043 Ok ( None ) => {
10431044 debug ! (
1044- "Warning: Could not resolve database ID for bot '{}'. \
1045- This bot may not be installed in the organization .",
1046- user_login
1045+ "Bot '{}' not found in organization '{}'; skipping bypass actor . \
1046+ Verify the bot is installed and has access .",
1047+ user_login, org
10471048 ) ;
10481049 }
10491050 Err ( e) => {
1050- anyhow:: bail!(
1051- "Failed to resolve database ID for bot '{}': {}" ,
1052- user_login,
1053- e
1054- ) ;
1051+ return Err ( e) . context ( format ! (
1052+ "Failed to resolve bot '{}' for bypass actor" ,
1053+ user_login
1054+ ) ) ;
10551055 }
10561056 }
10571057 }
@@ -1355,21 +1355,9 @@ impl std::fmt::Display for CreateRepoDiff {
13551355
13561356 if !rulesets. is_empty ( ) {
13571357 writeln ! ( f, " Rulesets:" ) ?;
1358- for ( ruleset, _branch_protection) in rulesets {
1359- let id_str = ruleset. id . map_or ( "new" . to_string ( ) , |id| id. to_string ( ) ) ;
1360- writeln ! ( f, " - {} (ID: {})" , ruleset. name, id_str) ?;
1361- if let Some ( conditions) = & ruleset. conditions
1362- && let Some ( ref_name) = & conditions. ref_name
1363- {
1364- writeln ! ( f, " Branches: {}" , ref_name. include. join( ", " ) ) ?;
1365- }
1366- if ruleset
1367- . rules
1368- . iter ( )
1369- . any ( |r| matches ! ( r, api:: RulesetRule :: MergeQueue { .. } ) )
1370- {
1371- writeln ! ( f, " Merge Queue: Enabled" ) ?;
1372- }
1358+ for ( ruleset, branch_protection) in rulesets {
1359+ writeln ! ( f, " {}" , ruleset. name) ?;
1360+ log_ruleset ( ruleset, None , Some ( branch_protection) , & mut f) ?;
13731361 }
13741362 }
13751363
@@ -1805,6 +1793,7 @@ fn log_branch_protection(
18051793fn log_ruleset (
18061794 current : & api:: Ruleset ,
18071795 new : Option < & api:: Ruleset > ,
1796+ branch_protection : Option < & rust_team_data:: v1:: BranchProtection > ,
18081797 mut result : impl Write ,
18091798) -> std:: fmt:: Result {
18101799 macro_rules! log {
@@ -1860,7 +1849,28 @@ fn log_ruleset(
18601849 && !bypass_actors. is_empty ( )
18611850 {
18621851 let new_bypass = new. and_then ( |n| n. bypass_actors . as_ref ( ) ) ;
1863- log ! ( "Bypass Actors" , bypass_actors, new_bypass) ;
1852+ if new_bypass. is_some ( ) {
1853+ log ! ( "Bypass Actors" , bypass_actors, new_bypass) ;
1854+ } else {
1855+ writeln ! ( result, " Bypass Actors:" ) ?;
1856+ for actor in bypass_actors {
1857+ let actor_id_str = match & actor. actor_id {
1858+ Some ( api:: ActorId :: Id ( id) ) => id. to_string ( ) ,
1859+ Some ( api:: ActorId :: Null ) => "null" . to_string ( ) ,
1860+ None => "omitted" . to_string ( ) ,
1861+ } ;
1862+ let mode_str = actor. bypass_mode . as_ref ( ) . map_or ( "default" , |m| match m {
1863+ api:: RulesetBypassMode :: Always => "always" ,
1864+ api:: RulesetBypassMode :: PullRequest => "pull_request" ,
1865+ api:: RulesetBypassMode :: Exempt => "exempt" ,
1866+ } ) ;
1867+ writeln ! (
1868+ result,
1869+ " - Type: {:?}, ID: {}, Mode: {}" ,
1870+ actor. actor_type, actor_id_str, mode_str
1871+ ) ?;
1872+ }
1873+ }
18641874 }
18651875
18661876 // Log individual rules
@@ -1977,6 +1987,36 @@ fn log_ruleset(
19771987 }
19781988 }
19791989
1990+ // Log expected bypass actors if branch protection config is provided
1991+ if let Some ( bp) = branch_protection {
1992+ if !bp. allowed_merge_teams . is_empty ( )
1993+ || !bp. allowed_merge_users . is_empty ( )
1994+ || !bp. merge_bots . is_empty ( )
1995+ {
1996+ writeln ! ( result, " Expected Bypass Actors:" ) ?;
1997+
1998+ for team in & bp. allowed_merge_teams {
1999+ writeln ! ( result, " - Team: {} (Mode: always)" , team) ?;
2000+ }
2001+
2002+ if !bp. allowed_merge_users . is_empty ( ) {
2003+ writeln ! (
2004+ result,
2005+ " - OrganizationAdmin: ID=1 (Mode: always) [Note: Grants bypass to all org admins, requested users: {}]" ,
2006+ bp. allowed_merge_users. join( ", " )
2007+ ) ?;
2008+ }
2009+
2010+ for bot in & bp. merge_bots {
2011+ let bot_name = match bot {
2012+ MergeBot :: Homu => "bors" ,
2013+ MergeBot :: RustTimer => "rust-timer" ,
2014+ } ;
2015+ writeln ! ( result, " - Integration: {} (Mode: always)" , bot_name) ?;
2016+ }
2017+ }
2018+ }
2019+
19802020 Ok ( ( ) )
19812021}
19822022
@@ -2051,8 +2091,12 @@ impl std::fmt::Display for RulesetDiff {
20512091 fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
20522092 writeln ! ( f, " {}" , self . name) ?;
20532093 match & self . operation {
2054- RulesetDiffOperation :: Create ( ruleset) => log_ruleset ( ruleset, None , f) ,
2055- RulesetDiffOperation :: Update ( _, old, new) => log_ruleset ( old, Some ( new) , f) ,
2094+ RulesetDiffOperation :: Create ( ruleset) => {
2095+ log_ruleset ( ruleset, None , Some ( & self . branch_protection ) , & mut * f)
2096+ }
2097+ RulesetDiffOperation :: Update ( _, old, new) => {
2098+ log_ruleset ( old, Some ( new) , Some ( & self . branch_protection ) , & mut * f)
2099+ }
20562100 RulesetDiffOperation :: Delete ( _) => {
20572101 writeln ! ( f, " Deleting ruleset" )
20582102 }
0 commit comments