8
8
//! If the command was successful, there will be no feedback beyond the label change to reduce
9
9
//! notification noise.
10
10
11
+ use std:: collections:: BTreeSet ;
12
+
13
+ use crate :: github:: Label ;
11
14
use crate :: team_data:: TeamClient ;
12
15
use crate :: {
13
16
config:: RelabelConfig ,
@@ -24,8 +27,11 @@ pub(super) async fn handle_command(
24
27
event : & Event ,
25
28
input : RelabelCommand ,
26
29
) -> 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
29
35
for delta in & input. 0 {
30
36
let name = delta. label ( ) . as_str ( ) ;
31
37
let err = match check_filter ( name, config, is_member ( & event. user ( ) , & ctx. team ) . await ) {
@@ -42,54 +48,37 @@ pub(super) async fn handle_command(
42
48
Err ( err) => Some ( err) ,
43
49
} ;
44
50
if let Some ( msg) = err {
45
- let cmnt = ErrorComment :: new ( & event . issue ( ) . unwrap ( ) , msg) ;
51
+ let cmnt = ErrorComment :: new ( issue, msg) ;
46
52
cmnt. post ( & ctx. github ) . await ?;
47
53
return Ok ( ( ) ) ;
48
54
}
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
- }
62
55
}
63
56
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 {
70
62
tracing:: error!(
71
63
"failed to add {:?} from issue {}: {:?}" ,
72
64
to_add,
73
- event . issue( ) . unwrap ( ) . global_id( ) ,
65
+ issue. global_id( ) ,
74
66
e
75
67
) ;
76
68
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 ?;
82
70
}
83
71
84
72
return Err ( e) ;
85
73
}
86
74
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 {
89
78
tracing:: error!(
90
79
"failed to remove {:?} from issue {}: {:?}" ,
91
80
label,
92
- event . issue( ) . unwrap ( ) . global_id( ) ,
81
+ issue. global_id( ) ,
93
82
e
94
83
) ;
95
84
return Err ( e) ;
@@ -182,10 +171,41 @@ fn match_pattern(pattern: &str, label: &str) -> anyhow::Result<MatchPatternResul
182
171
} )
183
172
}
184
173
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
+
185
202
#[ cfg( test) ]
186
203
mod tests {
204
+ use parser:: command:: relabel:: { Label , LabelDelta } ;
205
+
187
206
use super :: {
188
- CheckFilterResult , MatchPatternResult , TeamMembership , check_filter, match_pattern,
207
+ CheckFilterResult , MatchPatternResult , TeamMembership , check_filter, compute_label_deltas,
208
+ match_pattern,
189
209
} ;
190
210
use crate :: config:: RelabelConfig ;
191
211
@@ -252,4 +272,55 @@ mod tests {
252
272
}
253
273
Ok ( ( ) )
254
274
}
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
+ }
255
326
}
0 commit comments