|
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 | + pub 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)] |
@@ -725,6 +780,8 @@ where |
725 | 780 | #[cfg(test)] |
726 | 781 | mod tests { |
727 | 782 | use super::*; |
| 783 | + use parser::error::Error; |
| 784 | + use parser::token::Tokenizer; |
728 | 785 |
|
729 | 786 | #[test] |
730 | 787 | fn sample() { |
@@ -761,11 +818,11 @@ mod tests { |
761 | 818 |
|
762 | 819 | [mentions."src/"] |
763 | 820 | cc = ["@someone"] |
764 | | - |
| 821 | +
|
765 | 822 | [mentions."target/"] |
766 | 823 | message = "This is a message." |
767 | 824 | cc = ["@someone"] |
768 | | - |
| 825 | +
|
769 | 826 | [mentions."#[rustc_attr]"] |
770 | 827 | type = "content" |
771 | 828 | message = "This is a message." |
@@ -835,6 +892,7 @@ mod tests { |
835 | 892 | Config { |
836 | 893 | relabel: Some(RelabelConfig { |
837 | 894 | allow_unauthenticated: vec!["C-*".into()], |
| 895 | + aliases: HashMap::new() |
838 | 896 | }), |
839 | 897 | assign: Some(AssignConfig { |
840 | 898 | warn_non_default_branch: WarnNonDefaultBranchConfig::Simple(false), |
@@ -1033,6 +1091,82 @@ mod tests { |
1033 | 1091 | ); |
1034 | 1092 | } |
1035 | 1093 |
|
| 1094 | + #[test] |
| 1095 | + fn relabel_alias_config() { |
| 1096 | + let config = r#" |
| 1097 | + [relabel.to-stable] |
| 1098 | + add-labels = ["regression-from-stable-to-stable"] |
| 1099 | + rem-labels = ["regression-from-stable-to-beta", "regression-from-stable-to-nightly"] |
| 1100 | + "#; |
| 1101 | + let config = toml::from_str::<Config>(&config).unwrap(); |
| 1102 | + |
| 1103 | + let mut relabel_configs = HashMap::new(); |
| 1104 | + relabel_configs.insert( |
| 1105 | + "to-stable".into(), |
| 1106 | + RelabelAliasConfig { |
| 1107 | + add_labels: vec!["regression-from-stable-to-stable".to_string()], |
| 1108 | + rem_labels: vec![ |
| 1109 | + "regression-from-stable-to-beta".to_string(), |
| 1110 | + "regression-from-stable-to-nightly".to_string(), |
| 1111 | + ], |
| 1112 | + }, |
| 1113 | + ); |
| 1114 | + |
| 1115 | + let expected_cfg = RelabelConfig { |
| 1116 | + allow_unauthenticated: vec![], |
| 1117 | + aliases: relabel_configs, |
| 1118 | + }; |
| 1119 | + |
| 1120 | + assert_eq!(config.relabel, Some(expected_cfg)); |
| 1121 | + } |
| 1122 | + |
| 1123 | + #[cfg(test)] |
| 1124 | + fn parse<'a>(input: &'a str) -> Result<Option<Vec<LabelDelta>>, Error<'a>> { |
| 1125 | + let mut toks = Tokenizer::new(input); |
| 1126 | + Ok(RelabelCommand::parse(&mut toks)?.map(|c| c.0)) |
| 1127 | + } |
| 1128 | + |
| 1129 | + #[test] |
| 1130 | + fn relabel_alias() { |
| 1131 | + // [relabel.my-alias] |
| 1132 | + // add-labels = ["Alpha"] |
| 1133 | + // rem-labels = ["Bravo", "Charlie"] |
| 1134 | + let relabel_cfg = RelabelConfig { |
| 1135 | + allow_unauthenticated: vec![], |
| 1136 | + aliases: HashMap::from([( |
| 1137 | + "my-alias".to_string(), |
| 1138 | + RelabelAliasConfig { |
| 1139 | + add_labels: vec!["Alpha".to_string()], |
| 1140 | + rem_labels: vec!["Bravo".to_string(), "Charlie".to_string()], |
| 1141 | + }, |
| 1142 | + )]), |
| 1143 | + }; |
| 1144 | + |
| 1145 | + // @triagebot label my-alias |
| 1146 | + let deltas = parse("label my-alias").unwrap().unwrap(); |
| 1147 | + let new_input = relabel_cfg.retrieve_command_from_alias(RelabelCommand(deltas)); |
| 1148 | + assert_eq!( |
| 1149 | + new_input, |
| 1150 | + RelabelCommand(vec![ |
| 1151 | + LabelDelta::Add(Label("Alpha".into())), |
| 1152 | + LabelDelta::Remove(Label("Bravo".into())), |
| 1153 | + LabelDelta::Remove(Label("Charlie".into())), |
| 1154 | + ]) |
| 1155 | + ); |
| 1156 | + |
| 1157 | + // @triagebot label -my-alias |
| 1158 | + let deltas = parse("label -my-alias").unwrap().unwrap(); |
| 1159 | + let new_input = relabel_cfg.retrieve_command_from_alias(RelabelCommand(deltas)); |
| 1160 | + assert_eq!( |
| 1161 | + new_input, |
| 1162 | + RelabelCommand(vec![ |
| 1163 | + LabelDelta::Add(Label("Bravo".into())), |
| 1164 | + LabelDelta::Add(Label("Charlie".into())), |
| 1165 | + LabelDelta::Remove(Label("Alpha".into())), |
| 1166 | + ]) |
| 1167 | + ); |
| 1168 | + } |
| 1169 | + |
1036 | 1170 | #[test] |
1037 | 1171 | fn issue_links_uncanonicalized() { |
1038 | 1172 | let config = r#" |
@@ -1093,4 +1227,36 @@ Multi text body with ${mcp_issue} and ${mcp_title} |
1093 | 1227 | }) |
1094 | 1228 | ); |
1095 | 1229 | } |
| 1230 | + |
| 1231 | + #[test] |
| 1232 | + fn relabel_new_config() { |
| 1233 | + let config = r#" |
| 1234 | + [relabel] |
| 1235 | + allow-unauthenticated = ["ABCD-*"] |
| 1236 | +
|
| 1237 | + [relabel.to-stable] |
| 1238 | + add-labels = ["regression-from-stable-to-stable"] |
| 1239 | + rem-labels = ["regression-from-stable-to-beta", "regression-from-stable-to-nightly"] |
| 1240 | + "#; |
| 1241 | + let config = toml::from_str::<Config>(&config).unwrap(); |
| 1242 | + |
| 1243 | + let mut relabel_configs = HashMap::new(); |
| 1244 | + relabel_configs.insert( |
| 1245 | + "to-stable".into(), |
| 1246 | + RelabelAliasConfig { |
| 1247 | + add_labels: vec!["regression-from-stable-to-stable".to_string()], |
| 1248 | + rem_labels: vec![ |
| 1249 | + "regression-from-stable-to-beta".to_string(), |
| 1250 | + "regression-from-stable-to-nightly".to_string(), |
| 1251 | + ], |
| 1252 | + }, |
| 1253 | + ); |
| 1254 | + |
| 1255 | + let expected_cfg = RelabelConfig { |
| 1256 | + allow_unauthenticated: vec!["ABCD-*".to_string()], |
| 1257 | + aliases: relabel_configs, |
| 1258 | + }; |
| 1259 | + |
| 1260 | + assert_eq!(config.relabel, Some(expected_cfg)); |
| 1261 | + } |
1096 | 1262 | } |
0 commit comments