Skip to main content

breezyshim/
lib.rs

1//! This crate contains a rust wrapper for the Breezy API, which is written in Python.
2//!
3//! Breezy itself is being ported to Rust, but until that port has completed, this crate allows
4//! access to the most important Breezy APIs via Rust.
5//!
6//! The Rust API here will follow the Breezy 4.0 Rust API as much as possible, to make porting
7//! easier.
8//!
9//! # Example
10//!
11//! ```no_run
12//! use breezyshim::prelude::*;
13//! use breezyshim::branch::open as open_branch;
14//! breezyshim::plugin::load_plugins();
15//! let b = open_branch(&"https://code.launchpad.net/brz".parse().unwrap()).unwrap();
16//! println!("Last revision: {:?}", b.last_revision());
17//! ```
18
19#![deny(missing_docs)]
20// Necessary for pyo3, which uses the gil-refs feature in macros
21// which is not defined in breezyshim
22#![allow(unexpected_cfgs)]
23// TODO: Fix large error enum variants by boxing large fields
24#![allow(clippy::result_large_err)]
25
26pub mod bazaar;
27pub mod branch;
28pub mod clean_tree;
29pub mod commit;
30pub mod config;
31pub mod controldir;
32pub mod cvs;
33pub mod darcs;
34pub mod delta;
35pub mod diff;
36#[cfg(feature = "dirty-tracker")]
37pub mod dirty_tracker;
38pub mod error;
39pub mod export;
40pub mod foreign;
41pub mod forge;
42pub mod fossil;
43pub mod git;
44pub mod github;
45pub mod gitlab;
46pub mod gpg;
47pub mod graph;
48/// Group compression versioned files implementation
49pub mod groupcompress;
50pub mod hooks;
51pub mod interrepository;
52pub mod intertree;
53/// Knit versioned files implementation
54pub mod knit;
55#[cfg(feature = "launchpad")]
56pub mod launchpad;
57pub mod location;
58pub mod lock;
59pub mod mercurial;
60pub mod merge;
61pub mod osutils;
62pub mod patches;
63pub mod plugin;
64pub mod prelude;
65pub mod rename_map;
66pub mod repository;
67pub mod revisionid;
68pub mod status;
69pub mod subversion;
70pub mod tags;
71pub mod testing;
72pub mod transform;
73pub mod transport;
74pub mod tree;
75pub mod ui;
76pub mod urlutils;
77pub mod version;
78/// Versioned files API for storing file content history
79pub mod versionedfiles;
80/// Weave versioned files implementation
81pub mod weave;
82pub mod workingtree;
83pub mod workspace;
84
85#[cfg(feature = "debian")]
86pub mod debian;
87
88// Re-export core types and functions
89/// Branch trait representing a branch in a version control system
90pub use branch::Branch;
91/// Control directory traits and types
92pub use controldir::{ControlDir, Prober};
93/// Forge related types and functions for interacting with source code hosting services
94pub use forge::{get_forge, Forge, MergeProposal, MergeProposalStatus};
95/// Lock type for managing access to resources
96pub use lock::Lock;
97use pyo3::exceptions::PyImportError;
98use pyo3::prelude::*;
99/// Revision identifier type
100pub use revisionid::RevisionId;
101/// Transport functions and types for accessing remote repositories
102pub use transport::{get_transport, Transport};
103/// Tree-related traits and types
104pub use tree::{RevisionTree, Tree, WorkingTree};
105/// URL utility functions
106pub use urlutils::{join_segment_parameters, split_segment_parameters};
107/// Workspace functions
108pub use workspace::reset_tree;
109
110/// Initialize Git support in Breezy.
111///
112/// This function imports the breezy.git module to ensure Git functionality is available.
113pub fn init_git() {
114    pyo3::Python::attach(|py| {
115        py.import("breezy.git").unwrap();
116    })
117}
118
119/// Initialize Bazaar support in Breezy.
120///
121/// This function imports the breezy.bzr module to ensure Bazaar functionality is available.
122pub fn init_bzr() {
123    pyo3::Python::attach(|py| {
124        py.import("breezy.bzr").unwrap();
125    })
126}
127
128#[cfg(feature = "auto-initialize")]
129/// Initialize
130#[ctor::ctor]
131fn ensure_initialized() {
132    init();
133}
134
135/// The minimum supported Breezy version.
136const MINIMUM_VERSION: (usize, usize, usize) = (3, 3, 6);
137
138/// Error returned when Breezy initialization fails.
139#[derive(Debug, Clone)]
140pub enum BreezyInitError {
141    /// Breezy is not installed.
142    NotInstalled,
143    /// The installed Breezy version is too old.
144    VersionTooOld {
145        /// The installed version.
146        installed: (usize, usize, usize),
147        /// The minimum required version.
148        required: (usize, usize, usize),
149    },
150    /// Some other error occurred during initialization.
151    Other(String),
152}
153
154impl std::fmt::Display for BreezyInitError {
155    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156        match self {
157            BreezyInitError::NotInstalled => {
158                write!(f, "Breezy is not installed. Please install Breezy first.")
159            }
160            BreezyInitError::VersionTooOld {
161                installed,
162                required,
163            } => write!(
164                f,
165                "Breezy version {}.{}.{} is too old, please upgrade to at least {}.{}.{}.",
166                installed.0, installed.1, installed.2, required.0, required.1, required.2
167            ),
168            BreezyInitError::Other(e) => write!(f, "{}", e),
169        }
170    }
171}
172
173impl std::error::Error for BreezyInitError {}
174
175/// Initialization lock to ensure Breezy is only initialized once.
176static INIT_BREEZY: std::sync::OnceLock<std::result::Result<(), BreezyInitError>> =
177    std::sync::OnceLock::new();
178
179fn do_init() -> std::result::Result<(), BreezyInitError> {
180    pyo3::Python::initialize();
181    let (major, minor, micro) = pyo3::Python::attach(|py| match py.import("breezy") {
182        Ok(breezy) => {
183            let (major, minor, micro, _releaselevel, _serial): (
184                usize,
185                usize,
186                usize,
187                String,
188                usize,
189            ) = breezy.getattr("version_info").unwrap().extract().unwrap();
190            Ok((major, minor, micro))
191        }
192        Err(e) => {
193            if e.is_instance_of::<PyImportError>(py) {
194                Err(BreezyInitError::NotInstalled)
195            } else {
196                Err(BreezyInitError::Other(e.to_string()))
197            }
198        }
199    })?;
200
201    if (major, minor, micro) < MINIMUM_VERSION {
202        return Err(BreezyInitError::VersionTooOld {
203            installed: (major, minor, micro),
204            required: MINIMUM_VERSION,
205        });
206    }
207
208    if major >= 4 {
209        log::warn!("Support for Breezy 4.0 is experimental and incomplete.");
210    }
211
212    init_git();
213    init_bzr();
214
215    // Work around a breezy bug
216    pyo3::Python::attach(|py| {
217        let m = py.import("breezy.controldir").unwrap();
218        let f = m.getattr("ControlDirFormat").unwrap();
219        f.call_method0("known_formats").unwrap();
220    });
221
222    // Prevent race conditions
223    pyo3::Python::attach(|py| {
224        let m = py.import("breezy.config").unwrap();
225        m.call_method0("GlobalStack").unwrap();
226        m.call_method1("LocationStack", ("file:///",)).unwrap();
227    });
228
229    Ok(())
230}
231
232/// Try to initialize the Breezy library and Python interpreter.
233///
234/// Returns `Ok(())` if initialization succeeded, or a [`BreezyInitError`] if it failed.
235/// The result is cached after the first call.
236pub fn try_init() -> std::result::Result<(), BreezyInitError> {
237    INIT_BREEZY.get_or_init(do_init).clone()
238}
239
240/// Initialize the Breezy library and Python interpreter.
241///
242/// This function ensures Python is initialized and Breezy is loaded.
243/// It is safe to call multiple times.
244///
245/// # Panics
246///
247/// - If Breezy is not installed
248/// - If the installed Breezy version is too old
249pub fn init() {
250    try_init().unwrap();
251}
252
253/// Shorthand for the standard result type used throughout this crate.
254pub type Result<R> = std::result::Result<R, crate::error::Error>;