Skip to content

Commit a46114b

Browse files
authored
Merge pull request #2189 from Urgau/relabel-deltas-left-right
Apply labels from left to right in relabel
2 parents b0a31bc + ced76f0 commit a46114b

File tree

3 files changed

+105
-34
lines changed

3 files changed

+105
-34
lines changed

parser/src/command/relabel.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ pub enum LabelDelta {
1919
}
2020

2121
#[derive(Debug, PartialEq, Eq, Clone)]
22-
pub struct Label(String);
22+
pub struct Label(pub String);
2323

2424
#[derive(PartialEq, Eq, Debug)]
2525
pub enum ParseError {

src/github.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ impl User {
324324
}
325325
}
326326

327-
#[derive(PartialEq, Eq, Debug, Clone, serde::Deserialize)]
327+
#[derive(PartialEq, Eq, Debug, Clone, Ord, PartialOrd, serde::Deserialize)]
328328
pub struct Label {
329329
pub name: String,
330330
}

src/handlers/relabel.rs

Lines changed: 103 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
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;
1114
use crate::team_data::TeamClient;
1215
use 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)]
186203
mod 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

Comments
 (0)