|
1 | 1 | use crate::changelogs::ChangelogFormat; |
2 | 2 | use crate::github::{GithubClient, Repository}; |
| 3 | +use parser::command::relabel::{Label, LabelDelta, RelabelCommand}; |
3 | 4 | use std::collections::{HashMap, HashSet}; |
4 | 5 | use std::fmt; |
5 | 6 | use std::sync::{Arc, LazyLock, RwLock}; |
@@ -250,10 +251,64 @@ pub(crate) struct MentionsEntryConfig { |
250 | 251 |
|
251 | 252 | #[derive(PartialEq, Eq, Debug, serde::Deserialize)] |
252 | 253 | #[serde(rename_all = "kebab-case")] |
253 | | -#[serde(deny_unknown_fields)] |
254 | 254 | pub(crate) struct RelabelConfig { |
255 | 255 | #[serde(default)] |
256 | 256 | pub(crate) allow_unauthenticated: Vec<String>, |
| 257 | + // alias identifier -> labels |
| 258 | + #[serde(flatten)] |
| 259 | + pub(crate) aliases: HashMap<String, RelabelAliasConfig>, |
| 260 | +} |
| 261 | + |
| 262 | +impl RelabelConfig { |
| 263 | + pub(crate) fn retrieve_command_from_alias(&self, input: RelabelCommand) -> RelabelCommand { |
| 264 | + let mut deltas = vec![]; |
| 265 | + if !self.aliases.is_empty() { |
| 266 | + // parse all tokens: if one matches an alias, extract the labels |
| 267 | + // else, it will assumed to be a valid label |
| 268 | + for tk in input.0.into_iter() { |
| 269 | + let name = tk.label() as &str; |
| 270 | + if let Some(alias) = self.aliases.get(name) { |
| 271 | + let cmd = alias.to_command(matches!(tk, LabelDelta::Remove(_))); |
| 272 | + deltas.extend(cmd.0); |
| 273 | + } else { |
| 274 | + deltas.push(tk); |
| 275 | + } |
| 276 | + } |
| 277 | + } |
| 278 | + RelabelCommand(deltas) |
| 279 | + } |
| 280 | +} |
| 281 | + |
| 282 | +#[derive(Default, PartialEq, Eq, Debug, serde::Deserialize)] |
| 283 | +#[serde(rename_all = "kebab-case")] |
| 284 | +#[serde(deny_unknown_fields)] |
| 285 | +pub(crate) struct RelabelAliasConfig { |
| 286 | + /// Labels to be added |
| 287 | + pub(crate) add_labels: Vec<String>, |
| 288 | + /// Labels to be removed |
| 289 | + pub(crate) rem_labels: Vec<String>, |
| 290 | +} |
| 291 | + |
| 292 | +impl RelabelAliasConfig { |
| 293 | + /// Translate a RelabelAliasConfig into a RelabelCommand for GitHub consumption |
| 294 | + fn to_command(&self, inverted: bool) -> RelabelCommand { |
| 295 | + let mut deltas = Vec::new(); |
| 296 | + let mut add_labels = &self.add_labels; |
| 297 | + let mut rem_labels = &self.rem_labels; |
| 298 | + |
| 299 | + // if the polarity of the alias is inverted, swap labels before parsing the command |
| 300 | + if inverted { |
| 301 | + std::mem::swap(&mut add_labels, &mut rem_labels); |
| 302 | + } |
| 303 | + |
| 304 | + for l in add_labels.iter() { |
| 305 | + deltas.push(LabelDelta::Add(Label(l.into()))); |
| 306 | + } |
| 307 | + for l in rem_labels.iter() { |
| 308 | + deltas.push(LabelDelta::Remove(Label(l.into()))); |
| 309 | + } |
| 310 | + RelabelCommand(deltas) |
| 311 | + } |
257 | 312 | } |
258 | 313 |
|
259 | 314 | #[derive(PartialEq, Eq, Debug, serde::Deserialize)] |
@@ -761,11 +816,11 @@ mod tests { |
761 | 816 |
|
762 | 817 | [mentions."src/"] |
763 | 818 | cc = ["@someone"] |
764 | | - |
| 819 | +
|
765 | 820 | [mentions."target/"] |
766 | 821 | message = "This is a message." |
767 | 822 | cc = ["@someone"] |
768 | | - |
| 823 | +
|
769 | 824 | [mentions."#[rustc_attr]"] |
770 | 825 | type = "content" |
771 | 826 | message = "This is a message." |
@@ -835,6 +890,7 @@ mod tests { |
835 | 890 | Config { |
836 | 891 | relabel: Some(RelabelConfig { |
837 | 892 | allow_unauthenticated: vec!["C-*".into()], |
| 893 | + aliases: HashMap::new() |
838 | 894 | }), |
839 | 895 | assign: Some(AssignConfig { |
840 | 896 | warn_non_default_branch: WarnNonDefaultBranchConfig::Simple(false), |
@@ -1033,6 +1089,76 @@ mod tests { |
1033 | 1089 | ); |
1034 | 1090 | } |
1035 | 1091 |
|
| 1092 | + #[test] |
| 1093 | + fn relabel_alias_config() { |
| 1094 | + let config = r#" |
| 1095 | + [relabel.to-stable] |
| 1096 | + add-labels = ["regression-from-stable-to-stable"] |
| 1097 | + rem-labels = ["regression-from-stable-to-beta", "regression-from-stable-to-nightly"] |
| 1098 | + "#; |
| 1099 | + let config = toml::from_str::<Config>(&config).unwrap(); |
| 1100 | + |
| 1101 | + let mut relabel_configs = HashMap::new(); |
| 1102 | + relabel_configs.insert( |
| 1103 | + "to-stable".into(), |
| 1104 | + RelabelAliasConfig { |
| 1105 | + add_labels: vec!["regression-from-stable-to-stable".to_string()], |
| 1106 | + rem_labels: vec![ |
| 1107 | + "regression-from-stable-to-beta".to_string(), |
| 1108 | + "regression-from-stable-to-nightly".to_string(), |
| 1109 | + ], |
| 1110 | + }, |
| 1111 | + ); |
| 1112 | + |
| 1113 | + let expected_cfg = RelabelConfig { |
| 1114 | + allow_unauthenticated: vec![], |
| 1115 | + aliases: relabel_configs, |
| 1116 | + }; |
| 1117 | + |
| 1118 | + assert_eq!(config.relabel, Some(expected_cfg)); |
| 1119 | + } |
| 1120 | + |
| 1121 | + #[test] |
| 1122 | + fn relabel_alias() { |
| 1123 | + // [relabel.my-alias] |
| 1124 | + // add-labels = ["Alpha"] |
| 1125 | + // rem-labels = ["Bravo", "Charlie"] |
| 1126 | + let relabel_cfg = RelabelConfig { |
| 1127 | + allow_unauthenticated: vec![], |
| 1128 | + aliases: HashMap::from([( |
| 1129 | + "my-alias".to_string(), |
| 1130 | + RelabelAliasConfig { |
| 1131 | + add_labels: vec!["Alpha".to_string()], |
| 1132 | + rem_labels: vec!["Bravo".to_string(), "Charlie".to_string()], |
| 1133 | + }, |
| 1134 | + )]), |
| 1135 | + }; |
| 1136 | + |
| 1137 | + // @triagebot label my-alias |
| 1138 | + let deltas = vec![LabelDelta::Add(Label("my-alias".into()))]; |
| 1139 | + let new_input = relabel_cfg.retrieve_command_from_alias(RelabelCommand(deltas)); |
| 1140 | + assert_eq!( |
| 1141 | + new_input, |
| 1142 | + RelabelCommand(vec![ |
| 1143 | + LabelDelta::Add(Label("Alpha".into())), |
| 1144 | + LabelDelta::Remove(Label("Bravo".into())), |
| 1145 | + LabelDelta::Remove(Label("Charlie".into())), |
| 1146 | + ]) |
| 1147 | + ); |
| 1148 | + |
| 1149 | + // @triagebot label -my-alias |
| 1150 | + let deltas = vec![LabelDelta::Remove(Label("my-alias".into()))]; |
| 1151 | + let new_input = relabel_cfg.retrieve_command_from_alias(RelabelCommand(deltas)); |
| 1152 | + assert_eq!( |
| 1153 | + new_input, |
| 1154 | + RelabelCommand(vec![ |
| 1155 | + LabelDelta::Add(Label("Bravo".into())), |
| 1156 | + LabelDelta::Add(Label("Charlie".into())), |
| 1157 | + LabelDelta::Remove(Label("Alpha".into())), |
| 1158 | + ]) |
| 1159 | + ); |
| 1160 | + } |
| 1161 | + |
1036 | 1162 | #[test] |
1037 | 1163 | fn issue_links_uncanonicalized() { |
1038 | 1164 | let config = r#" |
@@ -1093,4 +1219,36 @@ Multi text body with ${mcp_issue} and ${mcp_title} |
1093 | 1219 | }) |
1094 | 1220 | ); |
1095 | 1221 | } |
| 1222 | + |
| 1223 | + #[test] |
| 1224 | + fn relabel_new_config() { |
| 1225 | + let config = r#" |
| 1226 | + [relabel] |
| 1227 | + allow-unauthenticated = ["ABCD-*"] |
| 1228 | +
|
| 1229 | + [relabel.to-stable] |
| 1230 | + add-labels = ["regression-from-stable-to-stable"] |
| 1231 | + rem-labels = ["regression-from-stable-to-beta", "regression-from-stable-to-nightly"] |
| 1232 | + "#; |
| 1233 | + let config = toml::from_str::<Config>(&config).unwrap(); |
| 1234 | + |
| 1235 | + let mut relabel_configs = HashMap::new(); |
| 1236 | + relabel_configs.insert( |
| 1237 | + "to-stable".into(), |
| 1238 | + RelabelAliasConfig { |
| 1239 | + add_labels: vec!["regression-from-stable-to-stable".to_string()], |
| 1240 | + rem_labels: vec![ |
| 1241 | + "regression-from-stable-to-beta".to_string(), |
| 1242 | + "regression-from-stable-to-nightly".to_string(), |
| 1243 | + ], |
| 1244 | + }, |
| 1245 | + ); |
| 1246 | + |
| 1247 | + let expected_cfg = RelabelConfig { |
| 1248 | + allow_unauthenticated: vec!["ABCD-*".to_string()], |
| 1249 | + aliases: relabel_configs, |
| 1250 | + }; |
| 1251 | + |
| 1252 | + assert_eq!(config.relabel, Some(expected_cfg)); |
| 1253 | + } |
1096 | 1254 | } |
0 commit comments