@@ -6,7 +6,7 @@ use self::api::{BranchProtectionOp, TeamPrivacy, TeamRole};
6
6
use crate :: github:: api:: { GithubRead , Login , PushAllowanceActor , RepoPermission , RepoSettings } ;
7
7
use log:: debug;
8
8
use rust_team_data:: v1:: { Bot , BranchProtectionMode , MergeBot } ;
9
- use std:: collections:: { HashMap , HashSet } ;
9
+ use std:: collections:: { BTreeMap , HashMap , HashSet } ;
10
10
use std:: fmt:: Write ;
11
11
12
12
pub ( crate ) use self :: api:: { GitHubApiRead , GitHubWrite , HttpClient } ;
@@ -31,6 +31,7 @@ struct SyncGitHub {
31
31
repos : Vec < rust_team_data:: v1:: Repo > ,
32
32
usernames_cache : HashMap < u64 , String > ,
33
33
org_owners : HashMap < OrgName , HashSet < u64 > > ,
34
+ org_members : HashMap < OrgName , HashMap < u64 , String > > ,
34
35
}
35
36
36
37
impl SyncGitHub {
@@ -60,9 +61,11 @@ impl SyncGitHub {
60
61
. collect :: < HashSet < _ > > ( ) ;
61
62
62
63
let mut org_owners = HashMap :: new ( ) ;
64
+ let mut org_members = HashMap :: new ( ) ;
63
65
64
66
for org in & orgs {
65
67
org_owners. insert ( ( * org) . to_string ( ) , github. org_owners ( org) ?) ;
68
+ org_members. insert ( ( * org) . to_string ( ) , github. org_members ( org) ?) ;
66
69
}
67
70
68
71
Ok ( SyncGitHub {
@@ -71,19 +74,74 @@ impl SyncGitHub {
71
74
repos,
72
75
usernames_cache,
73
76
org_owners,
77
+ org_members,
74
78
} )
75
79
}
76
80
77
81
pub ( crate ) fn diff_all ( & self ) -> anyhow:: Result < Diff > {
78
82
let team_diffs = self . diff_teams ( ) ?;
79
83
let repo_diffs = self . diff_repos ( ) ?;
84
+ let org_membership_diffs = self . diff_org_memberships ( ) ?;
80
85
81
86
Ok ( Diff {
82
87
team_diffs,
83
88
repo_diffs,
89
+ org_membership_diffs,
84
90
} )
85
91
}
86
92
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
+
87
145
fn diff_teams ( & self ) -> anyhow:: Result < Vec < TeamDiff > > {
88
146
let mut diffs = Vec :: new ( ) ;
89
147
let mut unseen_github_teams = HashMap :: new ( ) ;
@@ -584,6 +642,7 @@ const BOTS_TEAMS: &[&str] = &["bors", "highfive", "rfcbot", "bots"];
584
642
pub ( crate ) struct Diff {
585
643
team_diffs : Vec < TeamDiff > ,
586
644
repo_diffs : Vec < RepoDiff > ,
645
+ org_membership_diffs : Vec < OrgMembershipDiff > ,
587
646
}
588
647
589
648
impl Diff {
@@ -595,12 +654,17 @@ impl Diff {
595
654
for repo_diff in self . repo_diffs {
596
655
repo_diff. apply ( sync) ?;
597
656
}
657
+ for org_diff in self . org_membership_diffs {
658
+ org_diff. apply ( sync) ?;
659
+ }
598
660
599
661
Ok ( ( ) )
600
662
}
601
663
602
664
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 ( )
604
668
}
605
669
}
606
670
@@ -620,6 +684,13 @@ impl std::fmt::Display for Diff {
620
684
}
621
685
}
622
686
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
+
623
694
Ok ( ( ) )
624
695
}
625
696
}
@@ -655,6 +726,34 @@ impl std::fmt::Display for RepoDiff {
655
726
}
656
727
}
657
728
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
+
658
757
#[ derive( Debug ) ]
659
758
struct CreateRepoDiff {
660
759
org : String ,
0 commit comments