@@ -6,7 +6,7 @@ use self::api::{BranchProtectionOp, TeamPrivacy, TeamRole};
66use crate :: github:: api:: { GithubRead , Login , PushAllowanceActor , RepoPermission , RepoSettings } ;
77use log:: debug;
88use rust_team_data:: v1:: { Bot , BranchProtectionMode , MergeBot } ;
9- use std:: collections:: { HashMap , HashSet } ;
9+ use std:: collections:: { BTreeMap , HashMap , HashSet } ;
1010use std:: fmt:: Write ;
1111
1212pub ( crate ) use self :: api:: { GitHubApiRead , GitHubWrite , HttpClient } ;
@@ -31,6 +31,7 @@ struct SyncGitHub {
3131 repos : Vec < rust_team_data:: v1:: Repo > ,
3232 usernames_cache : HashMap < u64 , String > ,
3333 org_owners : HashMap < OrgName , HashSet < u64 > > ,
34+ org_members : HashMap < OrgName , HashMap < u64 , String > > ,
3435}
3536
3637impl SyncGitHub {
@@ -60,9 +61,11 @@ impl SyncGitHub {
6061 . collect :: < HashSet < _ > > ( ) ;
6162
6263 let mut org_owners = HashMap :: new ( ) ;
64+ let mut org_members = HashMap :: new ( ) ;
6365
6466 for org in & orgs {
6567 org_owners. insert ( ( * org) . to_string ( ) , github. org_owners ( org) ?) ;
68+ org_members. insert ( ( * org) . to_string ( ) , github. org_members ( org) ?) ;
6669 }
6770
6871 Ok ( SyncGitHub {
@@ -71,19 +74,74 @@ impl SyncGitHub {
7174 repos,
7275 usernames_cache,
7376 org_owners,
77+ org_members,
7478 } )
7579 }
7680
7781 pub ( crate ) fn diff_all ( & self ) -> anyhow:: Result < Diff > {
7882 let team_diffs = self . diff_teams ( ) ?;
7983 let repo_diffs = self . diff_repos ( ) ?;
84+ let org_membership_diffs = self . diff_org_memberships ( ) ?;
8085
8186 Ok ( Diff {
8287 team_diffs,
8388 repo_diffs,
89+ org_membership_diffs,
8490 } )
8591 }
8692
93+ // Collect all org members from the respective teams
94+ fn get_org_members_from_teams ( & self ) -> HashMap < OrgName , HashSet < u64 > > {
95+ let mut org_team_members: HashMap < OrgName , HashSet < u64 > > = HashMap :: new ( ) ;
96+
97+ for team in & self . teams {
98+ if let Some ( gh) = & team. github {
99+ for toml_gh_team in & gh. teams {
100+ org_team_members
101+ . entry ( toml_gh_team. org . clone ( ) )
102+ . or_default ( )
103+ . extend ( toml_gh_team. members . iter ( ) . copied ( ) ) ;
104+ }
105+ }
106+ }
107+ org_team_members
108+ }
109+
110+ // Diff organization memberships between TOML teams and GitHub
111+ fn diff_org_memberships ( & self ) -> anyhow:: Result < Vec < OrgMembershipDiff > > {
112+ let toml_org_team_members = self . get_org_members_from_teams ( ) ;
113+
114+ let mut org_diffs: BTreeMap < String , OrgMembershipDiff > = BTreeMap :: new ( ) ;
115+
116+ for ( org, toml_members) in toml_org_team_members {
117+ let Some ( gh_org_members) = self . org_members . get ( & org) else {
118+ panic ! ( "GitHub organization {org} not found" ) ;
119+ } ;
120+
121+ // Remove all members that are in TOML teams
122+ let mut members_to_remove = gh_org_members. clone ( ) ;
123+ for member in toml_members {
124+ members_to_remove. remove ( & member) ;
125+ }
126+
127+ // The rest are members that should be removed
128+ if !members_to_remove. is_empty ( ) {
129+ let mut members_to_remove: Vec < String > = members_to_remove. into_values ( ) . collect ( ) ;
130+ members_to_remove. sort ( ) ;
131+
132+ org_diffs. insert (
133+ org. clone ( ) ,
134+ OrgMembershipDiff {
135+ org,
136+ members_to_remove,
137+ } ,
138+ ) ;
139+ }
140+ }
141+
142+ Ok ( org_diffs. into_values ( ) . collect ( ) )
143+ }
144+
87145 fn diff_teams ( & self ) -> anyhow:: Result < Vec < TeamDiff > > {
88146 let mut diffs = Vec :: new ( ) ;
89147 let mut unseen_github_teams = HashMap :: new ( ) ;
@@ -584,6 +642,7 @@ const BOTS_TEAMS: &[&str] = &["bors", "highfive", "rfcbot", "bots"];
584642pub ( crate ) struct Diff {
585643 team_diffs : Vec < TeamDiff > ,
586644 repo_diffs : Vec < RepoDiff > ,
645+ org_membership_diffs : Vec < OrgMembershipDiff > ,
587646}
588647
589648impl Diff {
@@ -595,12 +654,17 @@ impl Diff {
595654 for repo_diff in self . repo_diffs {
596655 repo_diff. apply ( sync) ?;
597656 }
657+ for org_diff in self . org_membership_diffs {
658+ org_diff. apply ( sync) ?;
659+ }
598660
599661 Ok ( ( ) )
600662 }
601663
602664 pub ( crate ) fn is_empty ( & self ) -> bool {
603- self . team_diffs . is_empty ( ) && self . repo_diffs . is_empty ( )
665+ self . team_diffs . is_empty ( )
666+ && self . repo_diffs . is_empty ( )
667+ && self . org_membership_diffs . is_empty ( )
604668 }
605669}
606670
@@ -620,6 +684,13 @@ impl std::fmt::Display for Diff {
620684 }
621685 }
622686
687+ if !& self . org_membership_diffs . is_empty ( ) {
688+ writeln ! ( f, "💻 Org membership Diffs:" ) ?;
689+ for org_diff in & self . org_membership_diffs {
690+ write ! ( f, "{org_diff}" ) ?;
691+ }
692+ }
693+
623694 Ok ( ( ) )
624695 }
625696}
@@ -655,6 +726,34 @@ impl std::fmt::Display for RepoDiff {
655726 }
656727}
657728
729+ #[ derive( Debug ) ]
730+ struct OrgMembershipDiff {
731+ org : OrgName ,
732+ members_to_remove : Vec < String > ,
733+ }
734+
735+ impl OrgMembershipDiff {
736+ fn apply ( self , sync : & GitHubWrite ) -> anyhow:: Result < ( ) > {
737+ for member in & self . members_to_remove {
738+ sync. remove_gh_member_from_org ( & self . org , & member) ?;
739+ }
740+
741+ Ok ( ( ) )
742+ }
743+ }
744+
745+ impl std:: fmt:: Display for OrgMembershipDiff {
746+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
747+ if !self . members_to_remove . is_empty ( ) {
748+ writeln ! ( f, "❌ Removing the following members from `{}`:" , self . org) ?;
749+ for member in & self . members_to_remove {
750+ writeln ! ( f, " - {member}" , ) ?;
751+ }
752+ }
753+ Ok ( ( ) )
754+ }
755+ }
756+
658757#[ derive( Debug ) ]
659758struct CreateRepoDiff {
660759 org : String ,
0 commit comments