1#![deny(missing_docs)]
16#![allow(unexpected_cfgs)]
19pub mod batch;
20pub mod candidates;
21pub mod checks;
22pub mod codemod;
23#[cfg(feature = "debian")]
24pub mod debian;
25pub mod probers;
26pub mod proposal;
27pub mod publish;
28pub mod recipe;
29pub mod run;
30pub mod utils;
31pub mod vcs;
32pub mod workspace;
33pub use breezyshim::branch::{Branch, GenericBranch};
34pub use breezyshim::controldir::{ControlDir, Prober};
35pub use breezyshim::forge::{Forge, MergeProposal};
36pub use breezyshim::transport::Transport;
37pub use breezyshim::tree::WorkingTree;
38pub use breezyshim::RevisionId;
39use serde::{Deserialize, Deserializer, Serialize, Serializer};
40use std::path::Path;
41
42#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default)]
43pub enum Mode {
45 #[serde(rename = "push")]
46 Push,
48
49 #[serde(rename = "propose")]
50 Propose,
52
53 #[serde(rename = "attempt-push")]
54 #[default]
55 AttemptPush,
57
58 #[serde(rename = "push-derived")]
59 PushDerived,
61
62 #[serde(rename = "bts")]
63 Bts,
65}
66
67impl std::fmt::Display for Mode {
68 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69 let s = match self {
70 Mode::Push => "push",
71 Mode::Propose => "propose",
72 Mode::AttemptPush => "attempt-push",
73 Mode::PushDerived => "push-derived",
74 Mode::Bts => "bts",
75 };
76 write!(f, "{}", s)
77 }
78}
79
80impl std::str::FromStr for Mode {
81 type Err = String;
82
83 fn from_str(s: &str) -> Result<Self, Self::Err> {
84 match s {
85 "push" => Ok(Mode::Push),
86 "propose" => Ok(Mode::Propose),
87 "attempt" | "attempt-push" => Ok(Mode::AttemptPush),
88 "push-derived" => Ok(Mode::PushDerived),
89 "bts" => Ok(Mode::Bts),
90 _ => Err(format!("Unknown mode: {}", s)),
91 }
92 }
93}
94
95pub fn derived_branch_name(script: &str) -> &str {
97 let first_word = script.split(' ').next().unwrap_or("");
98 let script_name = Path::new(first_word).file_stem().unwrap_or_default();
99 script_name.to_str().unwrap_or("")
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
104pub enum CommitPending {
105 #[default]
107 Auto,
108
109 Yes,
111
112 No,
114}
115
116impl std::str::FromStr for CommitPending {
117 type Err = String;
118
119 fn from_str(s: &str) -> Result<Self, Self::Err> {
120 match s {
121 "auto" => Ok(CommitPending::Auto),
122 "yes" => Ok(CommitPending::Yes),
123 "no" => Ok(CommitPending::No),
124 _ => Err(format!("Unknown commit-pending value: {}", s)),
125 }
126 }
127}
128
129impl Serialize for CommitPending {
130 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
131 where
132 S: Serializer,
133 {
134 match *self {
135 CommitPending::Auto => serializer.serialize_none(),
136 CommitPending::Yes => serializer.serialize_bool(true),
137 CommitPending::No => serializer.serialize_bool(false),
138 }
139 }
140}
141
142impl<'de> Deserialize<'de> for CommitPending {
143 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
144 where
145 D: Deserializer<'de>,
146 {
147 let opt: Option<bool> = Option::deserialize(deserializer)?;
148 Ok(match opt {
149 None => CommitPending::Auto,
150 Some(true) => CommitPending::Yes,
151 Some(false) => CommitPending::No,
152 })
153 }
154}
155
156impl CommitPending {
157 pub fn is_default(&self) -> bool {
159 *self == CommitPending::Auto
160 }
161}
162
163pub trait CodemodResult {
165 fn context(&self) -> serde_json::Value;
167
168 fn value(&self) -> Option<u32>;
170
171 fn target_branch_url(&self) -> Option<url::Url>;
173
174 fn description(&self) -> Option<String>;
176
177 fn tags(&self) -> Vec<(String, Option<RevisionId>)>;
179
180 fn tera_context(&self) -> tera::Context {
182 tera::Context::from_value(self.context()).unwrap()
183 }
184}
185
186pub const VERSION: &str = env!("CARGO_PKG_VERSION");
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192 use std::str::FromStr;
193
194 #[test]
195 fn test_derived_branch_name() {
196 assert_eq!(derived_branch_name("script.py"), "script");
197 assert_eq!(derived_branch_name("path/to/script.py"), "script");
198 assert_eq!(derived_branch_name("/absolute/path/to/script.py"), "script");
199 assert_eq!(derived_branch_name("script.py arg1 arg2"), "script");
200 assert_eq!(derived_branch_name(""), "");
201 assert_eq!(derived_branch_name("script"), "script");
202 assert_eq!(derived_branch_name("no-extension."), "no-extension");
203 }
204
205 #[test]
206 fn test_commit_pending_from_str() {
207 assert_eq!(
208 CommitPending::from_str("auto").unwrap(),
209 CommitPending::Auto
210 );
211 assert_eq!(CommitPending::from_str("yes").unwrap(), CommitPending::Yes);
212 assert_eq!(CommitPending::from_str("no").unwrap(), CommitPending::No);
213
214 let err = CommitPending::from_str("invalid").unwrap_err();
215 assert_eq!(err, "Unknown commit-pending value: invalid");
216 }
217
218 #[test]
219 fn test_commit_pending_serialization() {
220 let auto_json = serde_json::to_string(&CommitPending::Auto).unwrap();
222 let yes_json = serde_json::to_string(&CommitPending::Yes).unwrap();
223 let no_json = serde_json::to_string(&CommitPending::No).unwrap();
224
225 assert_eq!(auto_json, "null");
226 assert_eq!(yes_json, "true");
227 assert_eq!(no_json, "false");
228
229 let auto: CommitPending = serde_json::from_str("null").unwrap();
231 let yes: CommitPending = serde_json::from_str("true").unwrap();
232 let no: CommitPending = serde_json::from_str("false").unwrap();
233
234 assert_eq!(auto, CommitPending::Auto);
235 assert_eq!(yes, CommitPending::Yes);
236 assert_eq!(no, CommitPending::No);
237 }
238
239 #[test]
240 fn test_commit_pending_is_default() {
241 assert!(CommitPending::Auto.is_default());
242 assert!(!CommitPending::Yes.is_default());
243 assert!(!CommitPending::No.is_default());
244 }
245
246 #[test]
247 fn test_mode_from_str() {
248 assert_eq!(Mode::from_str("push").unwrap(), Mode::Push);
249 assert_eq!(Mode::from_str("propose").unwrap(), Mode::Propose);
250 assert_eq!(Mode::from_str("attempt").unwrap(), Mode::AttemptPush);
251 assert_eq!(Mode::from_str("attempt-push").unwrap(), Mode::AttemptPush);
252 assert_eq!(Mode::from_str("push-derived").unwrap(), Mode::PushDerived);
253 assert_eq!(Mode::from_str("bts").unwrap(), Mode::Bts);
254
255 let err = Mode::from_str("invalid").unwrap_err();
256 assert_eq!(err, "Unknown mode: invalid");
257 }
258
259 #[test]
260 fn test_mode_to_string() {
261 assert_eq!(Mode::Push.to_string(), "push");
262 assert_eq!(Mode::Propose.to_string(), "propose");
263 assert_eq!(Mode::AttemptPush.to_string(), "attempt-push");
264 assert_eq!(Mode::PushDerived.to_string(), "push-derived");
265 assert_eq!(Mode::Bts.to_string(), "bts");
266 }
267}