88//! If the command was successful, there will be no feedback beyond the label change to reduce
99//! notification noise.
1010
11+ use std:: collections:: BTreeSet ;
12+
13+ use crate :: github:: Label ;
1114use crate :: team_data:: TeamClient ;
1215use crate :: {
1316 config:: RelabelConfig ,
@@ -24,8 +27,11 @@ pub(super) async fn handle_command(
2427 event : & Event ,
2528 input : RelabelCommand ,
2629) -> anyhow:: Result < ( ) > {
27- let mut results = vec ! [ ] ;
28- let mut to_add = vec ! [ ] ;
30+ let Some ( issue) = event. issue ( ) else {
31+ anyhow:: bail!( "event is not an issue" ) ;
32+ } ;
33+
34+ // Check label authorization for the current user
2935 for delta in & input. 0 {
3036 let name = delta. label ( ) . as_str ( ) ;
3137 let err = match check_filter ( name, config, is_member ( & event. user ( ) , & ctx. team ) . await ) {
@@ -42,54 +48,37 @@ pub(super) async fn handle_command(
4248 Err ( err) => Some ( err) ,
4349 } ;
4450 if let Some ( msg) = err {
45- let cmnt = ErrorComment :: new ( & event . issue ( ) . unwrap ( ) , msg) ;
51+ let cmnt = ErrorComment :: new ( issue, msg) ;
4652 cmnt. post ( & ctx. github ) . await ?;
4753 return Ok ( ( ) ) ;
4854 }
49- match delta {
50- LabelDelta :: Add ( label) => {
51- to_add. push ( github:: Label {
52- name : label. to_string ( ) ,
53- } ) ;
54- }
55- LabelDelta :: Remove ( label) => {
56- results. push ( (
57- label,
58- event. issue ( ) . unwrap ( ) . remove_label ( & ctx. github , & label) ,
59- ) ) ;
60- }
61- }
6255 }
6356
64- if let Err ( e) = event
65- . issue ( )
66- . unwrap ( )
67- . add_labels ( & ctx. github , to_add. clone ( ) )
68- . await
69- {
57+ // Compute the labels to add and remove
58+ let ( to_add, to_remove) = compute_label_deltas ( & input. 0 ) ;
59+
60+ // Add labels
61+ if let Err ( e) = issue. add_labels ( & ctx. github , to_add. clone ( ) ) . await {
7062 tracing:: error!(
7163 "failed to add {:?} from issue {}: {:?}" ,
7264 to_add,
73- event . issue( ) . unwrap ( ) . global_id( ) ,
65+ issue. global_id( ) ,
7466 e
7567 ) ;
7668 if let Some ( err @ UnknownLabels { .. } ) = e. downcast_ref ( ) {
77- event
78- . issue ( )
79- . unwrap ( )
80- . post_comment ( & ctx. github , & err. to_string ( ) )
81- . await ?;
69+ issue. post_comment ( & ctx. github , & err. to_string ( ) ) . await ?;
8270 }
8371
8472 return Err ( e) ;
8573 }
8674
87- for ( label, res) in results {
88- if let Err ( e) = res. await {
75+ // Remove labels
76+ for label in to_remove {
77+ if let Err ( e) = issue. remove_label ( & ctx. github , & label. name ) . await {
8978 tracing:: error!(
9079 "failed to remove {:?} from issue {}: {:?}" ,
9180 label,
92- event . issue( ) . unwrap ( ) . global_id( ) ,
81+ issue. global_id( ) ,
9382 e
9483 ) ;
9584 return Err ( e) ;
@@ -182,10 +171,41 @@ fn match_pattern(pattern: &str, label: &str) -> anyhow::Result<MatchPatternResul
182171 } )
183172}
184173
174+ fn compute_label_deltas ( deltas : & [ LabelDelta ] ) -> ( Vec < Label > , Vec < Label > ) {
175+ let mut add = BTreeSet :: new ( ) ;
176+ let mut remove = BTreeSet :: new ( ) ;
177+
178+ for delta in deltas {
179+ match delta {
180+ LabelDelta :: Add ( label) => {
181+ let label = Label {
182+ name : label. to_string ( ) ,
183+ } ;
184+ if !remove. remove ( & label) {
185+ add. insert ( label) ;
186+ }
187+ }
188+ LabelDelta :: Remove ( label) => {
189+ let label = Label {
190+ name : label. to_string ( ) ,
191+ } ;
192+ if !add. remove ( & label) {
193+ remove. insert ( label) ;
194+ }
195+ }
196+ }
197+ }
198+
199+ ( add. into_iter ( ) . collect ( ) , remove. into_iter ( ) . collect ( ) )
200+ }
201+
185202#[ cfg( test) ]
186203mod tests {
204+ use parser:: command:: relabel:: { Label , LabelDelta } ;
205+
187206 use super :: {
188- CheckFilterResult , MatchPatternResult , TeamMembership , check_filter, match_pattern,
207+ CheckFilterResult , MatchPatternResult , TeamMembership , check_filter, compute_label_deltas,
208+ match_pattern,
189209 } ;
190210 use crate :: config:: RelabelConfig ;
191211
@@ -252,4 +272,55 @@ mod tests {
252272 }
253273 Ok ( ( ) )
254274 }
275+
276+ #[ test]
277+ fn test_compute_label_deltas ( ) {
278+ use crate :: github:: Label as GitHubLabel ;
279+
280+ let mut deltas = vec ! [
281+ LabelDelta :: Add ( Label ( "I-nominated" . to_string( ) ) ) ,
282+ LabelDelta :: Add ( Label ( "I-nominated" . to_string( ) ) ) ,
283+ LabelDelta :: Add ( Label ( "I-lang-nominated" . to_string( ) ) ) ,
284+ LabelDelta :: Add ( Label ( "I-libs-nominated" . to_string( ) ) ) ,
285+ LabelDelta :: Remove ( Label ( "I-lang-nominated" . to_string( ) ) ) ,
286+ ] ;
287+
288+ assert_eq ! (
289+ compute_label_deltas( & deltas) ,
290+ (
291+ vec![
292+ GitHubLabel {
293+ name: "I-libs-nominated" . to_string( )
294+ } ,
295+ GitHubLabel {
296+ name: "I-nominated" . to_string( )
297+ } ,
298+ ] ,
299+ vec![ ] ,
300+ ) ,
301+ ) ;
302+
303+ deltas. push ( LabelDelta :: Remove ( Label ( "needs-triage" . to_string ( ) ) ) ) ;
304+ deltas. push ( LabelDelta :: Add ( Label ( "I-lang-nominated" . to_string ( ) ) ) ) ;
305+
306+ assert_eq ! (
307+ compute_label_deltas( & deltas) ,
308+ (
309+ vec![
310+ GitHubLabel {
311+ name: "I-lang-nominated" . to_string( )
312+ } ,
313+ GitHubLabel {
314+ name: "I-libs-nominated" . to_string( )
315+ } ,
316+ GitHubLabel {
317+ name: "I-nominated" . to_string( )
318+ } ,
319+ ] ,
320+ vec![ GitHubLabel {
321+ name: "needs-triage" . to_string( )
322+ } ] ,
323+ ) ,
324+ ) ;
325+ }
255326}
0 commit comments