|  | 
| 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