silver_platter/
lib.rs

1//! # Silver-Platter
2//!
3//! Silver-Platter makes it possible to contribute automatable changes to source
4//! code in a version control system
5//! ([codemods](https://github.com/jelmer/awesome-codemods)).
6//!
7//! It automatically creates a local checkout of a remote repository,
8//! makes user-specified changes, publishes those changes on the remote hosting
9//! site and then creates a pull request.
10//!
11//! In addition to that, it can also perform basic maintenance on branches
12//! that have been proposed for merging - such as restarting them if they
13//! have conflicts due to upstream changes.
14
15#![deny(missing_docs)]
16pub mod batch;
17pub mod candidates;
18pub mod checks;
19pub mod codemod;
20#[cfg(feature = "debian")]
21pub mod debian;
22pub mod probers;
23pub mod proposal;
24pub mod publish;
25pub mod recipe;
26pub mod run;
27pub mod utils;
28pub mod vcs;
29pub mod workspace;
30pub use breezyshim::branch::{Branch, GenericBranch};
31pub use breezyshim::controldir::{ControlDir, Prober};
32pub use breezyshim::forge::{Forge, MergeProposal};
33pub use breezyshim::transport::Transport;
34pub use breezyshim::tree::WorkingTree;
35pub use breezyshim::RevisionId;
36use serde::{Deserialize, Deserializer, Serialize, Serializer};
37use std::path::Path;
38
39#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default)]
40/// Publish mode
41pub enum Mode {
42    #[serde(rename = "push")]
43    /// Push to the target branch
44    Push,
45
46    #[serde(rename = "propose")]
47    /// Propose a merge
48    Propose,
49
50    #[serde(rename = "attempt-push")]
51    #[default]
52    /// Attempt to push to the target branch, falling back to propose if necessary
53    AttemptPush,
54
55    #[serde(rename = "push-derived")]
56    /// Push to a branch derived from the script name
57    PushDerived,
58
59    #[serde(rename = "bts")]
60    /// Bug tracking system
61    Bts,
62}
63
64impl ToString for Mode {
65    fn to_string(&self) -> String {
66        match self {
67            Mode::Push => "push".to_string(),
68            Mode::Propose => "propose".to_string(),
69            Mode::AttemptPush => "attempt-push".to_string(),
70            Mode::PushDerived => "push-derived".to_string(),
71            Mode::Bts => "bts".to_string(),
72        }
73    }
74}
75
76impl std::str::FromStr for Mode {
77    type Err = String;
78
79    fn from_str(s: &str) -> Result<Self, Self::Err> {
80        match s {
81            "push" => Ok(Mode::Push),
82            "propose" => Ok(Mode::Propose),
83            "attempt" | "attempt-push" => Ok(Mode::AttemptPush),
84            "push-derived" => Ok(Mode::PushDerived),
85            "bts" => Ok(Mode::Bts),
86            _ => Err(format!("Unknown mode: {}", s)),
87        }
88    }
89}
90
91#[cfg(feature = "pyo3")]
92impl pyo3::FromPyObject<'_> for Mode {
93    fn extract_bound(ob: &pyo3::Bound<pyo3::PyAny>) -> pyo3::PyResult<Self> {
94        use pyo3::prelude::*;
95        let s: std::borrow::Cow<str> = ob.extract()?;
96        match s.as_ref() {
97            "push" => Ok(Mode::Push),
98            "propose" => Ok(Mode::Propose),
99            "attempt-push" => Ok(Mode::AttemptPush),
100            "push-derived" => Ok(Mode::PushDerived),
101            "bts" => Ok(Mode::Bts),
102            _ => Err(pyo3::exceptions::PyValueError::new_err((format!(
103                "Unknown mode: {}",
104                s
105            ),))),
106        }
107    }
108}
109
110#[cfg(feature = "pyo3")]
111impl pyo3::ToPyObject for Mode {
112    fn to_object(&self, py: pyo3::Python) -> pyo3::PyObject {
113        self.to_string().to_object(py)
114    }
115}
116
117/// Returns the branch name derived from a script name
118pub fn derived_branch_name(script: &str) -> &str {
119    let first_word = script.split(' ').next().unwrap_or("");
120    let script_name = Path::new(first_word).file_stem().unwrap_or_default();
121    script_name.to_str().unwrap_or("")
122}
123
124/// Policy on whether to commit pending changes
125#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
126pub enum CommitPending {
127    /// Automatically determine pending changes
128    #[default]
129    Auto,
130
131    /// Commit pending changes
132    Yes,
133
134    /// Don't commit pending changes
135    No,
136}
137
138impl std::str::FromStr for CommitPending {
139    type Err = String;
140
141    fn from_str(s: &str) -> Result<Self, Self::Err> {
142        match s {
143            "auto" => Ok(CommitPending::Auto),
144            "yes" => Ok(CommitPending::Yes),
145            "no" => Ok(CommitPending::No),
146            _ => Err(format!("Unknown commit-pending value: {}", s)),
147        }
148    }
149}
150
151impl Serialize for CommitPending {
152    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
153    where
154        S: Serializer,
155    {
156        match *self {
157            CommitPending::Auto => serializer.serialize_none(),
158            CommitPending::Yes => serializer.serialize_bool(true),
159            CommitPending::No => serializer.serialize_bool(false),
160        }
161    }
162}
163
164impl<'de> Deserialize<'de> for CommitPending {
165    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
166    where
167        D: Deserializer<'de>,
168    {
169        let opt: Option<bool> = Option::deserialize(deserializer)?;
170        Ok(match opt {
171            None => CommitPending::Auto,
172            Some(true) => CommitPending::Yes,
173            Some(false) => CommitPending::No,
174        })
175    }
176}
177
178impl CommitPending {
179    /// Returns whether the policy is to commit pending changes
180    pub fn is_default(&self) -> bool {
181        *self == CommitPending::Auto
182    }
183}
184
185/// The result of a codemod
186pub trait CodemodResult {
187    /// Context
188    fn context(&self) -> serde_json::Value;
189
190    /// Returns the value of the result
191    fn value(&self) -> Option<u32>;
192
193    /// Returns the URL of the target branch
194    fn target_branch_url(&self) -> Option<url::Url>;
195
196    /// Returns the description of the result
197    fn description(&self) -> Option<String>;
198
199    /// Returns the tags of the result
200    fn tags(&self) -> Vec<(String, Option<RevisionId>)>;
201
202    /// Returns the context as a Tera context
203    fn tera_context(&self) -> tera::Context {
204        tera::Context::from_value(self.context()).unwrap()
205    }
206}
207
208/// The version of the library
209pub const VERSION: &str = env!("CARGO_PKG_VERSION");