silver_platter/
proposal.rs1use crate::vcs::{full_branch_url, open_branch};
3use breezyshim::branch::{Branch, GenericBranch};
4use breezyshim::error::Error as BrzError;
5pub use breezyshim::forge::MergeProposal;
6pub use breezyshim::forge::MergeProposalStatus;
7use breezyshim::forge::{iter_forge_instances, Forge};
8use serde::{Deserialize, Serialize};
9use std::str::FromStr;
10use url::Url;
11
12fn instance_iter_mps(
13 instance: Forge,
14 statuses: Option<Vec<MergeProposalStatus>>,
15) -> impl Iterator<Item = MergeProposal> {
16 let statuses = statuses.unwrap_or_else(|| vec![MergeProposalStatus::All]);
17 statuses
18 .into_iter()
19 .flat_map(
20 move |status| match instance.iter_my_proposals(Some(status), None) {
21 Ok(mps) => Some(mps),
22 Err(BrzError::ForgeLoginRequired) => {
23 log::warn!("Skipping forge {:?} because login is required", instance);
24 None
25 }
26 Err(e) => {
27 log::error!("Error listing merge proposals: {:?}", e);
28 None
29 }
30 },
31 )
32 .flatten()
33}
34
35pub fn iter_all_mps(
37 statuses: Option<Vec<MergeProposalStatus>>,
38) -> impl Iterator<Item = (Forge, MergeProposal)> {
39 let statuses = statuses.unwrap_or_else(|| vec![MergeProposalStatus::All]);
40 iter_forge_instances().flat_map(move |instance| {
41 instance_iter_mps(instance.clone(), Some(statuses.clone()))
42 .map(move |mp| (instance.clone(), mp))
43 })
44}
45
46pub fn iter_conflicted(
51 branch_name: &str,
52) -> impl Iterator<
53 Item = (
54 Url,
55 GenericBranch,
56 String,
57 GenericBranch,
58 Forge,
59 MergeProposal,
60 bool,
61 ),
62> + '_ {
63 let mut possible_transports = vec![];
64
65 iter_all_mps(Some(vec![MergeProposalStatus::Open])).filter_map(move |(forge, mp)| {
66 if mp.can_be_merged().unwrap() {
67 None
68 } else {
69 let main_branch = open_branch(
70 &mp.get_target_branch_url().unwrap().unwrap(),
71 Some(&mut possible_transports),
72 None,
73 None,
74 )
75 .unwrap();
76 let resume_branch = open_branch(
77 &mp.get_source_branch_url().unwrap().unwrap(),
78 Some(&mut possible_transports),
79 None,
80 None,
81 )
82 .unwrap();
83 if resume_branch.name().as_deref() != Some(branch_name)
84 && !(resume_branch.name().is_none()
85 && resume_branch.get_user_url().as_str().ends_with(branch_name))
86 {
87 None
88 } else {
89 let subpath = "";
91
92 Some((
93 full_branch_url(&resume_branch),
94 main_branch,
95 subpath.to_string(),
96 resume_branch,
97 forge,
98 mp,
99 true,
100 ))
101 }
102 }
103 })
104}
105
106#[derive(Debug, Serialize, Deserialize, Clone, Hash, PartialEq, Eq)]
107#[serde(rename_all = "kebab-case")]
108pub enum DescriptionFormat {
110 Markdown,
112
113 Html,
115
116 Plain,
118}
119
120impl FromStr for DescriptionFormat {
121 type Err = String;
122
123 fn from_str(s: &str) -> Result<Self, Self::Err> {
124 match s {
125 "markdown" => Ok(DescriptionFormat::Markdown),
126 "html" => Ok(DescriptionFormat::Html),
127 "plain" => Ok(DescriptionFormat::Plain),
128 _ => Err(format!("Unknown description format: {}", s)),
129 }
130 }
131}
132
133impl ToString for DescriptionFormat {
134 fn to_string(&self) -> String {
135 match self {
136 DescriptionFormat::Markdown => "markdown".to_string(),
137 DescriptionFormat::Html => "html".to_string(),
138 DescriptionFormat::Plain => "plain".to_string(),
139 }
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn test_description_format_from_str() {
149 assert_eq!(
150 DescriptionFormat::from_str("markdown").unwrap(),
151 DescriptionFormat::Markdown
152 );
153 assert_eq!(
154 DescriptionFormat::from_str("html").unwrap(),
155 DescriptionFormat::Html
156 );
157 assert_eq!(
158 DescriptionFormat::from_str("plain").unwrap(),
159 DescriptionFormat::Plain
160 );
161
162 assert!(DescriptionFormat::from_str("invalid").is_err());
164 let err = DescriptionFormat::from_str("invalid").unwrap_err();
165 assert_eq!(err, "Unknown description format: invalid");
166 }
167
168 #[test]
169 fn test_description_format_to_string() {
170 assert_eq!(DescriptionFormat::Markdown.to_string(), "markdown");
171 assert_eq!(DescriptionFormat::Html.to_string(), "html");
172 assert_eq!(DescriptionFormat::Plain.to_string(), "plain");
173 }
174
175 #[test]
176 fn test_description_format_serialization() {
177 let format = DescriptionFormat::Markdown;
179 let serialized = serde_json::to_string(&format).unwrap();
180 assert_eq!(serialized, "\"markdown\"");
181
182 let deserialized: DescriptionFormat = serde_json::from_str("\"markdown\"").unwrap();
184 assert_eq!(deserialized, DescriptionFormat::Markdown);
185
186 let deserialized: DescriptionFormat = serde_json::from_str("\"html\"").unwrap();
187 assert_eq!(deserialized, DescriptionFormat::Html);
188
189 let deserialized: DescriptionFormat = serde_json::from_str("\"plain\"").unwrap();
190 assert_eq!(deserialized, DescriptionFormat::Plain);
191 }
192}