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)]
16// Allow unknown cfgs for now, since import_exception_bound
17// expects a gil-refs feature that is not defined
18#![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)]
43/// Publish mode
44pub enum Mode {
45    #[serde(rename = "push")]
46    /// Push to the target branch
47    Push,
48
49    #[serde(rename = "propose")]
50    /// Propose a merge
51    Propose,
52
53    #[serde(rename = "attempt-push")]
54    #[default]
55    /// Attempt to push to the target branch, falling back to propose if necessary
56    AttemptPush,
57
58    #[serde(rename = "push-derived")]
59    /// Push to a branch derived from the script name
60    PushDerived,
61
62    #[serde(rename = "bts")]
63    /// Bug tracking system
64    Bts,
65}
66
67impl ToString for Mode {
68    fn to_string(&self) -> String {
69        match self {
70            Mode::Push => "push".to_string(),
71            Mode::Propose => "propose".to_string(),
72            Mode::AttemptPush => "attempt-push".to_string(),
73            Mode::PushDerived => "push-derived".to_string(),
74            Mode::Bts => "bts".to_string(),
75        }
76    }
77}
78
79impl std::str::FromStr for Mode {
80    type Err = String;
81
82    fn from_str(s: &str) -> Result<Self, Self::Err> {
83        match s {
84            "push" => Ok(Mode::Push),
85            "propose" => Ok(Mode::Propose),
86            "attempt" | "attempt-push" => Ok(Mode::AttemptPush),
87            "push-derived" => Ok(Mode::PushDerived),
88            "bts" => Ok(Mode::Bts),
89            _ => Err(format!("Unknown mode: {}", s)),
90        }
91    }
92}
93
94#[cfg(feature = "pyo3")]
95impl pyo3::FromPyObject<'_> for Mode {
96    fn extract_bound(ob: &pyo3::Bound<pyo3::PyAny>) -> pyo3::PyResult<Self> {
97        use pyo3::prelude::*;
98        let s: std::borrow::Cow<str> = ob.extract()?;
99        match s.as_ref() {
100            "push" => Ok(Mode::Push),
101            "propose" => Ok(Mode::Propose),
102            "attempt-push" => Ok(Mode::AttemptPush),
103            "push-derived" => Ok(Mode::PushDerived),
104            "bts" => Ok(Mode::Bts),
105            _ => Err(pyo3::exceptions::PyValueError::new_err((format!(
106                "Unknown mode: {}",
107                s
108            ),))),
109        }
110    }
111}
112
113#[cfg(feature = "pyo3")]
114impl pyo3::ToPyObject for Mode {
115    fn to_object(&self, py: pyo3::Python) -> pyo3::PyObject {
116        self.to_string().to_object(py)
117    }
118}
119
120/// Returns the branch name derived from a script name
121pub fn derived_branch_name(script: &str) -> &str {
122    let first_word = script.split(' ').next().unwrap_or("");
123    let script_name = Path::new(first_word).file_stem().unwrap_or_default();
124    script_name.to_str().unwrap_or("")
125}
126
127/// Policy on whether to commit pending changes
128#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
129pub enum CommitPending {
130    /// Automatically determine pending changes
131    #[default]
132    Auto,
133
134    /// Commit pending changes
135    Yes,
136
137    /// Don't commit pending changes
138    No,
139}
140
141impl std::str::FromStr for CommitPending {
142    type Err = String;
143
144    fn from_str(s: &str) -> Result<Self, Self::Err> {
145        match s {
146            "auto" => Ok(CommitPending::Auto),
147            "yes" => Ok(CommitPending::Yes),
148            "no" => Ok(CommitPending::No),
149            _ => Err(format!("Unknown commit-pending value: {}", s)),
150        }
151    }
152}
153
154impl Serialize for CommitPending {
155    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
156    where
157        S: Serializer,
158    {
159        match *self {
160            CommitPending::Auto => serializer.serialize_none(),
161            CommitPending::Yes => serializer.serialize_bool(true),
162            CommitPending::No => serializer.serialize_bool(false),
163        }
164    }
165}
166
167impl<'de> Deserialize<'de> for CommitPending {
168    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
169    where
170        D: Deserializer<'de>,
171    {
172        let opt: Option<bool> = Option::deserialize(deserializer)?;
173        Ok(match opt {
174            None => CommitPending::Auto,
175            Some(true) => CommitPending::Yes,
176            Some(false) => CommitPending::No,
177        })
178    }
179}
180
181impl CommitPending {
182    /// Returns whether the policy is to commit pending changes
183    pub fn is_default(&self) -> bool {
184        *self == CommitPending::Auto
185    }
186}
187
188/// The result of a codemod
189pub trait CodemodResult {
190    /// Context
191    fn context(&self) -> serde_json::Value;
192
193    /// Returns the value of the result
194    fn value(&self) -> Option<u32>;
195
196    /// Returns the URL of the target branch
197    fn target_branch_url(&self) -> Option<url::Url>;
198
199    /// Returns the description of the result
200    fn description(&self) -> Option<String>;
201
202    /// Returns the tags of the result
203    fn tags(&self) -> Vec<(String, Option<RevisionId>)>;
204
205    /// Returns the context as a Tera context
206    fn tera_context(&self) -> tera::Context {
207        tera::Context::from_value(self.context()).unwrap()
208    }
209}
210
211/// The version of the library
212pub const VERSION: &str = env!("CARGO_PKG_VERSION");