breezyshim/
controldir.rs

1//! The `ControlDir` class provides a high-level interface to control directories,
2//! e.g. ".bzr" or ".git" directories.
3use crate::branch::{py_tag_selector, Branch, GenericBranch, PyBranch};
4use crate::error::Error;
5use crate::repository::{GenericRepository, Repository};
6use crate::transport::Transport;
7use crate::workingtree::GenericWorkingTree;
8
9use crate::location::AsLocation;
10
11use pyo3::prelude::*;
12use pyo3::types::{PyDict, PyList};
13
14/// Trait for Python probers that can detect control directories.
15///
16/// This trait is implemented by prober types that wrap Python probers,
17/// which are used to detect the presence of control directories.
18pub trait PyProber: std::any::Any + std::fmt::Debug {
19    /// Get the underlying Python object for this prober.
20    fn to_object(&self, py: Python) -> Py<PyAny>;
21}
22
23/// Trait for probers that can detect control directories.
24///
25/// This trait defines the interface for probers, which are used to detect
26/// the presence of control directories (like .git or .bzr) in a location.
27pub trait Prober: std::fmt::Debug {
28    /// Check if a control directory exists at the location specified by a transport.
29    ///
30    /// # Parameters
31    ///
32    /// * `transport` - The transport to probe.
33    ///
34    /// # Returns
35    ///
36    /// `Ok(true)` if a control directory exists, `Ok(false)` if not, or an error
37    /// if the probe could not be completed.
38    fn probe_transport(&self, transport: &Transport) -> Result<bool, Error>;
39    /// Check if a control directory exists at the specified URL.
40    ///
41    /// # Parameters
42    ///
43    /// * `url` - The URL to probe.
44    ///
45    /// # Returns
46    ///
47    /// `Ok(true)` if a control directory exists, `Ok(false)` if not, or an error
48    /// if the probe could not be completed.
49    fn probe(&self, url: &url::Url) -> Result<bool, Error>;
50}
51
52impl<T: PyProber> Prober for T {
53    fn probe_transport(&self, transport: &Transport) -> Result<bool, Error> {
54        Python::attach(|py| {
55            let result = self.to_object(py).call_method1(
56                py,
57                "probe_transport",
58                (transport.as_pyobject(),),
59            )?;
60            Ok(result.extract(py)?)
61        })
62    }
63
64    fn probe(&self, url: &url::Url) -> Result<bool, Error> {
65        Python::attach(|py| {
66            let result = self
67                .to_object(py)
68                .call_method1(py, "probe", (url.to_string(),))?;
69            Ok(result.extract(py)?)
70        })
71    }
72}
73
74/// Trait for Python control directories.
75///
76/// This trait is implemented by control directory types that wrap Python
77/// control directory objects.
78pub trait PyControlDir: std::any::Any + std::fmt::Debug {
79    /// Get the underlying Python object for this control directory.
80    fn to_object(&self, py: Python) -> Py<PyAny>;
81}
82
83/// Trait for control directories.
84///
85/// A control directory is a directory that contains version control metadata,
86/// like .git or .bzr. This trait defines the interface for accessing and
87/// manipulating control directories.
88pub trait ControlDir: std::fmt::Debug {
89    /// Get a reference to self as Any for downcasting.
90    fn as_any(&self) -> &dyn std::any::Any;
91    /// The branch type associated with this control directory.
92    type Branch: Branch + ?Sized;
93    /// The repository type associated with this control directory.
94    type Repository: Repository;
95    /// The working tree type associated with this control directory.
96    type WorkingTree: crate::workingtree::WorkingTree;
97    /// Get the user-visible URL for this control directory.
98    ///
99    /// # Returns
100    ///
101    /// The URL that can be used to access this control directory.
102    fn get_user_url(&self) -> url::Url;
103    /// Get the format of this control directory.
104    ///
105    /// # Returns
106    ///
107    /// The format of this control directory.
108    fn get_format(&self) -> ControlDirFormat;
109    /// Get a transport for accessing this control directory's user files.
110    ///
111    /// # Returns
112    ///
113    /// A transport for accessing this control directory's user files.
114    fn user_transport(&self) -> Transport;
115    /// Get a transport for accessing this control directory's control files.
116    ///
117    /// # Returns
118    ///
119    /// A transport for accessing this control directory's control files.
120    fn control_transport(&self) -> Transport;
121    /// Open the repository in this control directory.
122    ///
123    /// # Returns
124    ///
125    /// The repository, or an error if the repository could not be opened.
126    fn open_repository(&self) -> Result<Self::Repository, Error>;
127    /// Find a repository in this control directory or its parents.
128    ///
129    /// # Returns
130    ///
131    /// The repository, or an error if no repository could be found.
132    fn find_repository(&self) -> Result<GenericRepository, Error>;
133    /// Get the format to use when cloning this control directory.
134    ///
135    /// # Returns
136    ///
137    /// The format to use when cloning this control directory.
138    fn cloning_metadir(&self) -> ControlDirFormat;
139    /// Create a new branch in this control directory.
140    ///
141    /// # Parameters
142    ///
143    /// * `name` - The name of the branch to create, or None for the default branch.
144    ///
145    /// # Returns
146    ///
147    /// The newly created branch, or an error if the branch could not be created.
148    fn create_branch(&self, name: Option<&str>) -> Result<Box<Self::Branch>, Error>;
149    /// Create a new repository in this control directory.
150    ///
151    /// # Parameters
152    ///
153    /// * `shared` - Whether the repository should be shared.
154    ///
155    /// # Returns
156    ///
157    /// The newly created repository, or an error if the repository could not be created.
158    fn create_repository(&self, shared: Option<bool>) -> Result<GenericRepository, Error>;
159    /// Open a branch in this control directory.
160    ///
161    /// # Parameters
162    ///
163    /// * `branch_name` - The name of the branch to open, or None for the default branch.
164    ///
165    /// # Returns
166    ///
167    /// The branch, or an error if the branch could not be opened.
168    fn open_branch(&self, branch_name: Option<&str>) -> Result<Box<Self::Branch>, Error>;
169    /// Create a working tree in this control directory.
170    ///
171    /// # Returns
172    ///
173    /// The newly created working tree, or an error if the working tree could not be created.
174    fn create_workingtree(&self) -> crate::Result<GenericWorkingTree>;
175    /// Set a branch reference in this control directory.
176    ///
177    /// # Parameters
178    ///
179    /// * `branch` - The branch to reference.
180    /// * `name` - The name to use for the reference, or None for the default name.
181    ///
182    /// # Returns
183    ///
184    /// `Ok(())` on success, or an error if the reference could not be set.
185    fn set_branch_reference(&self, branch: &dyn PyBranch, name: Option<&str>) -> crate::Result<()>;
186    /// Push a branch to this control directory.
187    ///
188    /// # Parameters
189    ///
190    /// * `source_branch` - The branch to push.
191    /// * `to_branch_name` - The name of the branch to push to, or None for the default name.
192    /// * `stop_revision` - The revision to stop pushing at, or None to push all revisions.
193    /// * `overwrite` - Whether to overwrite the target branch if it has diverged.
194    /// * `tag_selector` - A function that selects which tags to push, or None to push all tags.
195    ///
196    /// # Returns
197    ///
198    /// The target branch after the push, or an error if the push failed.
199    fn push_branch(
200        &self,
201        source_branch: &dyn PyBranch,
202        to_branch_name: Option<&str>,
203        stop_revision: Option<&crate::RevisionId>,
204        overwrite: Option<bool>,
205        tag_selector: Option<Box<dyn Fn(String) -> bool>>,
206    ) -> crate::Result<Box<Self::Branch>>;
207    /// Create a new control directory based on this one (similar to clone).
208    ///
209    /// # Parameters
210    ///
211    /// * `target` - The URL of the new control directory.
212    /// * `source_branch` - The branch to use as a source, or None to use the default branch.
213    /// * `create_tree_if_local` - Whether to create a working tree if the target is local.
214    /// * `stacked` - Whether the new branch should be stacked on this one.
215    /// * `revision_id` - The revision to sprout from, or None to use the last revision.
216    ///
217    /// # Returns
218    ///
219    /// The new control directory, or an error if it could not be created.
220    fn sprout(
221        &self,
222        target: url::Url,
223        source_branch: Option<&dyn PyBranch>,
224        create_tree_if_local: Option<bool>,
225        stacked: Option<bool>,
226        revision_id: Option<&crate::RevisionId>,
227    ) -> Result<
228        Box<
229            dyn ControlDir<
230                Branch = GenericBranch,
231                Repository = GenericRepository,
232                WorkingTree = crate::workingtree::GenericWorkingTree,
233            >,
234        >,
235        Error,
236    >;
237    /// Check if this control directory has a working tree.
238    ///
239    /// # Returns
240    ///
241    /// `true` if this control directory has a working tree, `false` otherwise.
242    fn has_workingtree(&self) -> bool;
243    /// Open the working tree in this control directory.
244    ///
245    /// # Returns
246    ///
247    /// The working tree, or an error if the working tree could not be opened.
248    fn open_workingtree(&self) -> crate::Result<GenericWorkingTree>;
249    /// Get the names of all branches in this control directory.
250    ///
251    /// # Returns
252    ///
253    /// A list of branch names, or an error if the branch names could not be retrieved.
254    fn branch_names(&self) -> crate::Result<Vec<String>>;
255
256    /// Check if a branch with the given name exists in this control directory.
257    ///
258    /// # Parameters
259    ///
260    /// * `name` - The name of the branch to check, or None for the default branch.
261    ///
262    /// # Returns
263    ///
264    /// `true` if the branch exists, `false` otherwise.
265    fn has_branch(&self, name: Option<&str>) -> bool;
266
267    /// Create both a branch and repository in this control directory.
268    ///
269    /// # Parameters
270    ///
271    /// * `name` - The name of the branch to create, or None for the default branch.
272    /// * `shared` - Whether the repository should be shared.
273    ///
274    /// # Returns
275    ///
276    /// The created branch, or an error if the branch could not be created.
277    fn create_branch_and_repo(
278        &self,
279        name: Option<&str>,
280        shared: Option<bool>,
281    ) -> Result<Box<Self::Branch>, Error>;
282
283    /// Get all branches in this control directory.
284    ///
285    /// # Returns
286    ///
287    /// A hashmap of branch names to branches, or an error if the branches could not be retrieved.
288    fn get_branches(&self) -> crate::Result<std::collections::HashMap<String, Box<Self::Branch>>>;
289
290    /// List all branches in this control directory.
291    ///
292    /// # Returns
293    ///
294    /// A list of branch names, or an error if the branches could not be listed.
295    fn list_branches(&self) -> crate::Result<Vec<String>>;
296
297    /// Find branches in the repository.
298    ///
299    /// # Parameters
300    ///
301    /// * `using` - Whether to use the repository's revisions.
302    ///
303    /// # Returns
304    ///
305    /// A vector of branches found, or an error if the branches could not be found.
306    fn find_branches(&self, using: Option<bool>) -> crate::Result<Vec<Box<Self::Branch>>>;
307
308    /// Get the reference location for a branch.
309    ///
310    /// # Parameters
311    ///
312    /// * `name` - The name of the branch, or None for the default branch.
313    ///
314    /// # Returns
315    ///
316    /// The branch reference location, or an error if the reference could not be found.
317    fn get_branch_reference(&self, name: Option<&str>) -> crate::Result<String>;
318
319    /// Check if this control directory can be converted to the given format.
320    ///
321    /// # Parameters
322    ///
323    /// * `format` - The format to check conversion to.
324    ///
325    /// # Returns
326    ///
327    /// `true` if conversion is possible, `false` otherwise.
328    fn can_convert_format(&self, format: &ControlDirFormat) -> bool;
329
330    /// Check if the target format is a valid conversion target.
331    ///
332    /// # Parameters
333    ///
334    /// * `target_format` - The format to check as a conversion target.
335    ///
336    /// # Returns
337    ///
338    /// An error if the target format is not valid for conversion.
339    fn check_conversion_target(&self, target_format: &ControlDirFormat) -> crate::Result<()>;
340
341    /// Check if this control directory needs format conversion.
342    ///
343    /// # Parameters
344    ///
345    /// * `format` - The format to check against.
346    ///
347    /// # Returns
348    ///
349    /// `true` if format conversion is needed, `false` otherwise.
350    fn needs_format_conversion(&self, format: Option<&ControlDirFormat>) -> bool;
351
352    /// Destroy the branch in this control directory.
353    ///
354    /// # Parameters
355    ///
356    /// * `name` - The name of the branch to destroy, or None for the default branch.
357    ///
358    /// # Returns
359    ///
360    /// An error if the branch could not be destroyed.
361    fn destroy_branch(&self, name: Option<&str>) -> crate::Result<()>;
362
363    /// Destroy the repository in this control directory.
364    ///
365    /// # Returns
366    ///
367    /// An error if the repository could not be destroyed.
368    fn destroy_repository(&self) -> crate::Result<()>;
369
370    /// Destroy the working tree in this control directory.
371    ///
372    /// # Returns
373    ///
374    /// An error if the working tree could not be destroyed.
375    fn destroy_workingtree(&self) -> crate::Result<()>;
376
377    /// Destroy the working tree metadata in this control directory.
378    ///
379    /// # Returns
380    ///
381    /// An error if the working tree metadata could not be destroyed.
382    fn destroy_workingtree_metadata(&self) -> crate::Result<()>;
383
384    /// Get the configuration for this control directory.
385    ///
386    /// # Returns
387    ///
388    /// A configuration stack for this control directory.
389    fn get_config(&self) -> crate::Result<crate::config::ConfigStack>;
390}
391
392/// A generic wrapper for a Python control directory object.
393///
394/// This struct wraps a Python control directory object and provides access to it
395/// through the ControlDir trait.
396pub struct GenericControlDir(Py<PyAny>);
397
398impl<'py> IntoPyObject<'py> for GenericControlDir {
399    type Target = PyAny;
400    type Output = Bound<'py, Self::Target>;
401    type Error = std::convert::Infallible;
402
403    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
404        Ok(self.0.into_bound(py))
405    }
406}
407
408impl<'a, 'py> FromPyObject<'a, 'py> for GenericControlDir {
409    type Error = PyErr;
410
411    fn extract(obj: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
412        Ok(GenericControlDir(obj.to_owned().unbind()))
413    }
414}
415
416impl PyControlDir for GenericControlDir {
417    fn to_object(&self, py: Python) -> Py<PyAny> {
418        self.0.clone_ref(py)
419    }
420}
421
422impl GenericControlDir {
423    /// Create a new GenericControlDir from a Python control directory object.
424    ///
425    /// # Parameters
426    ///
427    /// * `obj` - A Python object representing a control directory.
428    ///
429    /// # Returns
430    ///
431    /// A new GenericControlDir instance.
432    pub fn new(obj: Py<PyAny>) -> Self {
433        Self(obj)
434    }
435}
436
437impl<T: PyControlDir> ControlDir for T {
438    fn as_any(&self) -> &dyn std::any::Any {
439        self
440    }
441    type Branch = GenericBranch;
442    type Repository = crate::repository::GenericRepository;
443    type WorkingTree = crate::workingtree::GenericWorkingTree;
444    fn get_user_url(&self) -> url::Url {
445        Python::attach(|py| {
446            let result = self.to_object(py).getattr(py, "user_url").unwrap();
447            url::Url::parse(&result.extract::<String>(py).unwrap()).unwrap()
448        })
449    }
450
451    fn get_format(&self) -> ControlDirFormat {
452        Python::attach(|py| {
453            let result = self.to_object(py).getattr(py, "_format")?;
454            Ok::<_, PyErr>(ControlDirFormat(result))
455        })
456        .unwrap()
457    }
458
459    fn user_transport(&self) -> Transport {
460        Python::attach(|py| {
461            let result = self.to_object(py).getattr(py, "user_transport").unwrap();
462            crate::transport::Transport::new(result)
463        })
464    }
465
466    fn control_transport(&self) -> Transport {
467        Python::attach(|py| {
468            let result = self.to_object(py).getattr(py, "control_transport").unwrap();
469            crate::transport::Transport::new(result)
470        })
471    }
472
473    fn open_repository(&self) -> Result<GenericRepository, Error> {
474        Python::attach(|py| {
475            let result = self.to_object(py).call_method0(py, "open_repository")?;
476            Ok(GenericRepository::new(result))
477        })
478    }
479
480    fn find_repository(&self) -> Result<GenericRepository, Error> {
481        Python::attach(|py| {
482            let result = self.to_object(py).call_method0(py, "find_repository")?;
483            Ok(GenericRepository::new(result))
484        })
485    }
486
487    fn cloning_metadir(&self) -> ControlDirFormat {
488        Python::attach(|py| {
489            let result = self.to_object(py).call_method0(py, "cloning_metadir")?;
490            Ok::<_, PyErr>(ControlDirFormat(result))
491        })
492        .unwrap()
493    }
494
495    fn create_branch(&self, name: Option<&str>) -> Result<Box<Self::Branch>, Error> {
496        Python::attach(|py| {
497            let branch: Py<PyAny> =
498                self.to_object(py)
499                    .call_method(py, "create_branch", (name,), None)?;
500            Ok(Box::new(GenericBranch::from(branch)) as Box<Self::Branch>)
501        })
502    }
503
504    fn create_repository(&self, shared: Option<bool>) -> Result<GenericRepository, Error> {
505        Python::attach(|py| {
506            let kwargs = PyDict::new(py);
507            if let Some(shared) = shared {
508                kwargs.set_item("shared", shared)?;
509            }
510            let repository =
511                self.to_object(py)
512                    .call_method(py, "create_repository", (), Some(&kwargs))?;
513            Ok(GenericRepository::new(repository))
514        })
515    }
516
517    fn open_branch(&self, branch_name: Option<&str>) -> Result<Box<Self::Branch>, Error> {
518        Python::attach(|py| {
519            let branch: Py<PyAny> =
520                self.to_object(py)
521                    .call_method(py, "open_branch", (branch_name,), None)?;
522            Ok(Box::new(GenericBranch::from(branch)) as Box<Self::Branch>)
523        })
524    }
525
526    fn create_workingtree(&self) -> crate::Result<GenericWorkingTree> {
527        Python::attach(|py| {
528            let wt = self.to_object(py).call_method0(py, "create_workingtree")?;
529            Ok(GenericWorkingTree(wt))
530        })
531    }
532
533    fn set_branch_reference(&self, branch: &dyn PyBranch, name: Option<&str>) -> crate::Result<()> {
534        Python::attach(|py| {
535            self.to_object(py).call_method1(
536                py,
537                "set_branch_reference",
538                (&branch.to_object(py), name),
539            )?;
540            Ok(())
541        })
542    }
543
544    fn push_branch(
545        &self,
546        source_branch: &dyn PyBranch,
547        to_branch_name: Option<&str>,
548        stop_revision: Option<&crate::RevisionId>,
549        overwrite: Option<bool>,
550        tag_selector: Option<Box<dyn Fn(String) -> bool>>,
551    ) -> crate::Result<Box<Self::Branch>> {
552        Python::attach(|py| {
553            let kwargs = PyDict::new(py);
554            if let Some(to_branch_name) = to_branch_name {
555                kwargs.set_item("name", to_branch_name)?;
556            }
557            if let Some(tag_selector) = tag_selector {
558                kwargs.set_item("tag_selector", py_tag_selector(py, tag_selector)?)?;
559            }
560            if let Some(overwrite) = overwrite {
561                kwargs.set_item("overwrite", overwrite)?;
562            }
563            if let Some(stop_revision) = stop_revision {
564                kwargs.set_item("stop_revision", stop_revision.clone())?;
565            }
566            let result = self.to_object(py).call_method(
567                py,
568                "push_branch",
569                (&source_branch.to_object(py),),
570                Some(&kwargs),
571            )?;
572            Ok(
573                Box::new(GenericBranch::from(result.getattr(py, "target_branch")?))
574                    as Box<Self::Branch>,
575            )
576        })
577    }
578
579    fn sprout(
580        &self,
581        target: url::Url,
582        source_branch: Option<&dyn PyBranch>,
583        create_tree_if_local: Option<bool>,
584        stacked: Option<bool>,
585        revision_id: Option<&crate::RevisionId>,
586    ) -> Result<
587        Box<
588            dyn ControlDir<
589                Branch = GenericBranch,
590                Repository = GenericRepository,
591                WorkingTree = crate::workingtree::GenericWorkingTree,
592            >,
593        >,
594        Error,
595    > {
596        Python::attach(|py| {
597            let kwargs = PyDict::new(py);
598            if let Some(create_tree_if_local) = create_tree_if_local {
599                kwargs
600                    .set_item("create_tree_if_local", create_tree_if_local)
601                    .unwrap();
602            }
603            if let Some(stacked) = stacked {
604                kwargs.set_item("stacked", stacked).unwrap();
605            }
606            if let Some(source_branch) = source_branch {
607                kwargs
608                    .set_item("source_branch", source_branch.to_object(py))
609                    .unwrap();
610            }
611            if let Some(revision_id) = revision_id {
612                kwargs.set_item("revision_id", revision_id.clone()).unwrap();
613            }
614
615            let cd = self.to_object(py).call_method(
616                py,
617                "sprout",
618                (target.to_string(),),
619                Some(&kwargs),
620            )?;
621            Ok(Box::new(GenericControlDir(cd))
622                as Box<
623                    dyn ControlDir<
624                        Branch = GenericBranch,
625                        Repository = GenericRepository,
626                        WorkingTree = crate::workingtree::GenericWorkingTree,
627                    >,
628                >)
629        })
630    }
631
632    fn has_workingtree(&self) -> bool {
633        Python::attach(|py| {
634            let result = self
635                .to_object(py)
636                .call_method0(py, "has_workingtree")
637                .unwrap();
638            result.extract(py).unwrap()
639        })
640    }
641
642    fn open_workingtree(&self) -> crate::Result<GenericWorkingTree> {
643        Python::attach(|py| {
644            let wt = self.to_object(py).call_method0(py, "open_workingtree")?;
645            Ok(GenericWorkingTree(wt))
646        })
647    }
648
649    fn branch_names(&self) -> crate::Result<Vec<String>> {
650        Python::attach(|py| {
651            let names = self
652                .to_object(py)
653                .call_method0(py, "branch_names")?
654                .extract::<Vec<String>>(py)?;
655            Ok(names)
656        })
657    }
658
659    fn has_branch(&self, name: Option<&str>) -> bool {
660        Python::attach(|py| {
661            let result = self
662                .to_object(py)
663                .call_method1(py, "has_branch", (name,))
664                .unwrap();
665            result.extract(py).unwrap()
666        })
667    }
668
669    fn create_branch_and_repo(
670        &self,
671        name: Option<&str>,
672        shared: Option<bool>,
673    ) -> Result<Box<Self::Branch>, Error> {
674        Python::attach(|py| {
675            let kwargs = PyDict::new(py);
676            if let Some(shared) = shared {
677                kwargs.set_item("shared", shared)?;
678            }
679            let branch: Py<PyAny> = self.to_object(py).call_method(
680                py,
681                "create_branch_and_repo",
682                (name,),
683                Some(&kwargs),
684            )?;
685            Ok(Box::new(GenericBranch::from(branch)) as Box<Self::Branch>)
686        })
687    }
688
689    fn get_branches(&self) -> crate::Result<std::collections::HashMap<String, Box<Self::Branch>>> {
690        Python::attach(|py| {
691            let branches_dict = self.to_object(py).call_method0(py, "get_branches")?;
692            let mut branches = std::collections::HashMap::new();
693            let dict: &Bound<PyDict> = branches_dict
694                .cast_bound(py)
695                .map_err(|_| PyErr::new::<pyo3::exceptions::PyTypeError, _>("Expected a dict"))?;
696            for (key, value) in dict.iter() {
697                let name: String = key.extract()?;
698                let branch = GenericBranch::from(value.unbind());
699                branches.insert(name, Box::new(branch) as Box<Self::Branch>);
700            }
701            Ok(branches)
702        })
703    }
704
705    fn list_branches(&self) -> crate::Result<Vec<String>> {
706        Python::attach(|py| {
707            let names = self
708                .to_object(py)
709                .call_method0(py, "list_branches")?
710                .extract::<Vec<String>>(py)?;
711            Ok(names)
712        })
713    }
714
715    fn find_branches(&self, using: Option<bool>) -> crate::Result<Vec<Box<Self::Branch>>> {
716        Python::attach(|py| {
717            let kwargs = PyDict::new(py);
718            if let Some(using) = using {
719                kwargs.set_item("using", using)?;
720            }
721            let branches_list =
722                self.to_object(py)
723                    .call_method(py, "find_branches", (), Some(&kwargs))?;
724            let mut branches = Vec::new();
725            let list: &Bound<PyList> = branches_list
726                .cast_bound(py)
727                .map_err(|_| PyErr::new::<pyo3::exceptions::PyTypeError, _>("Expected a list"))?;
728            for item in list.iter() {
729                let branch = GenericBranch::from(item.unbind());
730                branches.push(Box::new(branch) as Box<Self::Branch>);
731            }
732            Ok(branches)
733        })
734    }
735
736    fn get_branch_reference(&self, name: Option<&str>) -> crate::Result<String> {
737        Python::attach(|py| {
738            let reference = self
739                .to_object(py)
740                .call_method1(py, "get_branch_reference", (name,))?
741                .extract::<String>(py)?;
742            Ok(reference)
743        })
744    }
745
746    fn can_convert_format(&self, format: &ControlDirFormat) -> bool {
747        Python::attach(|py| {
748            let result = self
749                .to_object(py)
750                .call_method1(py, "can_convert_format", (format.0.clone_ref(py),))
751                .unwrap();
752            result.extract(py).unwrap()
753        })
754    }
755
756    fn check_conversion_target(&self, target_format: &ControlDirFormat) -> crate::Result<()> {
757        Python::attach(|py| {
758            self.to_object(py).call_method1(
759                py,
760                "check_conversion_target",
761                (target_format.0.clone_ref(py),),
762            )?;
763            Ok(())
764        })
765    }
766
767    fn needs_format_conversion(&self, format: Option<&ControlDirFormat>) -> bool {
768        Python::attach(|py| {
769            let result = if let Some(format) = format {
770                self.to_object(py)
771                    .call_method1(py, "needs_format_conversion", (format.0.clone_ref(py),))
772                    .unwrap()
773            } else {
774                self.to_object(py)
775                    .call_method0(py, "needs_format_conversion")
776                    .unwrap()
777            };
778            result.extract(py).unwrap()
779        })
780    }
781
782    fn destroy_branch(&self, name: Option<&str>) -> crate::Result<()> {
783        Python::attach(|py| {
784            self.to_object(py)
785                .call_method1(py, "destroy_branch", (name,))?;
786            Ok(())
787        })
788    }
789
790    fn destroy_repository(&self) -> crate::Result<()> {
791        Python::attach(|py| {
792            self.to_object(py).call_method0(py, "destroy_repository")?;
793            Ok(())
794        })
795    }
796
797    fn destroy_workingtree(&self) -> crate::Result<()> {
798        Python::attach(|py| {
799            self.to_object(py).call_method0(py, "destroy_workingtree")?;
800            Ok(())
801        })
802    }
803
804    fn destroy_workingtree_metadata(&self) -> crate::Result<()> {
805        Python::attach(|py| {
806            self.to_object(py)
807                .call_method0(py, "destroy_workingtree_metadata")?;
808            Ok(())
809        })
810    }
811
812    fn get_config(&self) -> crate::Result<crate::config::ConfigStack> {
813        Python::attach(|py| {
814            let config = self.to_object(py).call_method0(py, "get_config")?;
815            Ok(crate::config::ConfigStack::new(config))
816        })
817    }
818}
819
820impl std::fmt::Debug for GenericControlDir {
821    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
822        f.write_fmt(format_args!("ControlDir({:?})", self.0))
823    }
824}
825
826/// The format of a control directory.
827///
828/// This struct represents the format of a control directory, which defines how
829/// the control directory is stored on disk and what capabilities it has.
830pub struct ControlDirFormat(Py<PyAny>);
831
832impl<'py> IntoPyObject<'py> for ControlDirFormat {
833    type Target = PyAny;
834    type Output = Bound<'py, Self::Target>;
835    type Error = std::convert::Infallible;
836
837    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
838        Ok(self.0.into_bound(py))
839    }
840}
841
842impl Clone for ControlDirFormat {
843    fn clone(&self) -> Self {
844        Python::attach(|py| ControlDirFormat(self.0.clone_ref(py)))
845    }
846}
847
848impl From<Py<PyAny>> for ControlDirFormat {
849    fn from(obj: Py<PyAny>) -> Self {
850        ControlDirFormat(obj)
851    }
852}
853
854impl Default for ControlDirFormat {
855    fn default() -> Self {
856        Python::attach(|py| {
857            let breezy = PyModule::import(py, "breezy.controldir").unwrap();
858            let cd_format = breezy.getattr("ControlDirFormat").unwrap();
859            let obj = cd_format.call_method0("get_default_format").unwrap();
860            assert!(!obj.is_none());
861            ControlDirFormat(obj.into())
862        })
863    }
864}
865
866impl ControlDirFormat {
867    /// Get the format string for this control directory format.
868    ///
869    /// # Returns
870    ///
871    /// The format string as a byte vector.
872    pub fn get_format_string(&self) -> Vec<u8> {
873        Python::attach(|py| {
874            self.0
875                .call_method0(py, "get_format_string")
876                .unwrap()
877                .extract(py)
878                .unwrap()
879        })
880    }
881
882    /// Get a human-readable description of this control directory format.
883    ///
884    /// # Returns
885    ///
886    /// A string describing this control directory format.
887    pub fn get_format_description(&self) -> String {
888        Python::attach(|py| {
889            self.0
890                .call_method0(py, "get_format_description")
891                .unwrap()
892                .extract(py)
893                .unwrap()
894        })
895    }
896
897    /// Check if a filename is a control filename in this format.
898    ///
899    /// # Parameters
900    ///
901    /// * `filename` - The filename to check.
902    ///
903    /// # Returns
904    ///
905    /// `true` if the filename is a control filename, `false` otherwise.
906    pub fn is_control_filename(&self, filename: &str) -> bool {
907        Python::attach(|py| {
908            self.0
909                .call_method1(py, "is_control_filename", (filename,))
910                .unwrap()
911                .extract(py)
912                .unwrap()
913        })
914    }
915
916    /// Initialize a control directory of this format on a transport.
917    ///
918    /// # Parameters
919    ///
920    /// * `transport` - The transport to initialize the control directory on.
921    ///
922    /// # Returns
923    ///
924    /// The initialized control directory, or an error if initialization failed.
925    pub fn initialize_on_transport(
926        &self,
927        transport: &Transport,
928    ) -> Result<
929        Box<
930            dyn ControlDir<
931                Branch = GenericBranch,
932                Repository = GenericRepository,
933                WorkingTree = crate::workingtree::GenericWorkingTree,
934            >,
935        >,
936        Error,
937    > {
938        Python::attach(|py| {
939            let cd =
940                self.0
941                    .call_method1(py, "initialize_on_transport", (transport.as_pyobject(),))?;
942            Ok(Box::new(GenericControlDir(cd))
943                as Box<
944                    dyn ControlDir<
945                        Branch = GenericBranch,
946                        Repository = GenericRepository,
947                        WorkingTree = crate::workingtree::GenericWorkingTree,
948                    >,
949                >)
950        })
951    }
952
953    /// Initialize a control directory of this format at a location.
954    ///
955    /// # Parameters
956    ///
957    /// * `location` - The location to initialize the control directory at.
958    ///
959    /// # Returns
960    ///
961    /// The initialized control directory, or an error if initialization failed.
962    pub fn initialize(
963        &self,
964        location: impl AsLocation,
965    ) -> Result<
966        Box<
967            dyn ControlDir<
968                Branch = GenericBranch,
969                Repository = GenericRepository,
970                WorkingTree = crate::workingtree::GenericWorkingTree,
971            >,
972        >,
973        Error,
974    > {
975        Python::attach(|py| {
976            let cd = self
977                .0
978                .call_method1(py, "initialize", (location.as_location(),))?;
979            Ok(Box::new(GenericControlDir(cd))
980                as Box<
981                    dyn ControlDir<
982                        Branch = GenericBranch,
983                        Repository = GenericRepository,
984                        WorkingTree = crate::workingtree::GenericWorkingTree,
985                    >,
986                >)
987        })
988    }
989}
990
991/// Open a tree or branch at a location.
992///
993/// # Parameters
994///
995/// * `location` - The location to open.
996/// * `name` - The name of the branch to open, or None for the default branch.
997/// * `possible_transports` - Optional list of transports to try.
998///
999/// # Returns
1000///
1001/// A tuple with an optional working tree (if one exists) and a branch, or an
1002/// error if neither could be opened.
1003pub fn open_tree_or_branch(
1004    location: impl AsLocation,
1005    name: Option<&str>,
1006    possible_transports: Option<&mut Vec<Transport>>,
1007) -> Result<(Option<GenericWorkingTree>, Box<dyn Branch>), Error> {
1008    Python::attach(|py| {
1009        let m = py.import("breezy.controldir")?;
1010        let cd = m.getattr("ControlDir")?;
1011
1012        let kwargs = PyDict::new(py);
1013        if let Some(possible_transports) = possible_transports {
1014            kwargs.set_item(
1015                "possible_transports",
1016                possible_transports
1017                    .iter()
1018                    .map(|t| t.as_pyobject().clone_ref(py))
1019                    .collect::<Vec<Py<PyAny>>>(),
1020            )?;
1021        }
1022
1023        let ret = cd.call_method(
1024            "open_tree_or_branch",
1025            (location.as_location(), name),
1026            Some(&kwargs),
1027        )?;
1028
1029        let (tree, branch) = ret.extract::<(Option<Py<PyAny>>, Py<PyAny>)>()?;
1030        let branch = Box::new(GenericBranch::from(branch)) as Box<dyn Branch>;
1031        let tree = tree.map(GenericWorkingTree);
1032        Ok((tree, branch))
1033    })
1034}
1035
1036/// Open a control directory at a location.
1037///
1038/// # Parameters
1039///
1040/// * `url` - The location to open.
1041/// * `possible_transports` - Optional list of transports to try.
1042///
1043/// # Returns
1044///
1045/// The control directory, or an error if one could not be opened.
1046pub fn open(
1047    url: impl AsLocation,
1048    possible_transports: Option<&mut Vec<Transport>>,
1049) -> Result<
1050    Box<
1051        dyn ControlDir<
1052            Branch = GenericBranch,
1053            Repository = GenericRepository,
1054            WorkingTree = crate::workingtree::GenericWorkingTree,
1055        >,
1056    >,
1057    Error,
1058> {
1059    Python::attach(|py| {
1060        let m = py.import("breezy.controldir")?;
1061        let cd = m.getattr("ControlDir")?;
1062        let kwargs = PyDict::new(py);
1063        if let Some(possible_transports) = possible_transports {
1064            kwargs.set_item(
1065                "possible_transports",
1066                possible_transports
1067                    .iter()
1068                    .map(|t| t.as_pyobject().clone_ref(py))
1069                    .collect::<Vec<Py<PyAny>>>(),
1070            )?;
1071        }
1072        let controldir = cd.call_method("open", (url.as_location(),), Some(&kwargs))?;
1073        Ok(Box::new(GenericControlDir(controldir.unbind()))
1074            as Box<
1075                dyn ControlDir<
1076                    Branch = GenericBranch,
1077                    Repository = GenericRepository,
1078                    WorkingTree = crate::workingtree::GenericWorkingTree,
1079                >,
1080            >)
1081    })
1082}
1083/// Create a new control directory at a location.
1084///
1085/// # Parameters
1086///
1087/// * `url` - The location to create the control directory at.
1088/// * `format` - The format to use for the new control directory.
1089/// * `possible_transports` - Optional list of transports to try.
1090///
1091/// # Returns
1092///
1093/// The newly created control directory, or an error if it could not be created.
1094pub fn create(
1095    url: impl AsLocation,
1096    format: impl AsFormat,
1097    possible_transports: Option<&mut Vec<Transport>>,
1098) -> Result<
1099    Box<
1100        dyn ControlDir<
1101            Branch = GenericBranch,
1102            Repository = GenericRepository,
1103            WorkingTree = crate::workingtree::GenericWorkingTree,
1104        >,
1105    >,
1106    Error,
1107> {
1108    Python::attach(|py| {
1109        let m = py.import("breezy.controldir")?;
1110        let cd = m.getattr("ControlDir")?;
1111        let kwargs = PyDict::new(py);
1112        if let Some(format) = format.as_format() {
1113            kwargs.set_item("format", format.clone())?;
1114        }
1115        if let Some(possible_transports) = possible_transports {
1116            kwargs.set_item(
1117                "possible_transports",
1118                possible_transports
1119                    .iter()
1120                    .map(|t| t.as_pyobject().clone_ref(py))
1121                    .collect::<Vec<Py<PyAny>>>(),
1122            )?;
1123        }
1124        let controldir = cd.call_method("create", (url.as_location(),), Some(&kwargs))?;
1125        Ok(Box::new(GenericControlDir(controldir.unbind()))
1126            as Box<
1127                dyn ControlDir<
1128                    Branch = GenericBranch,
1129                    Repository = GenericRepository,
1130                    WorkingTree = crate::workingtree::GenericWorkingTree,
1131                >,
1132            >)
1133    })
1134}
1135/// Create a new control directory on a transport.
1136///
1137/// # Parameters
1138///
1139/// * `transport` - The transport to create the control directory on.
1140/// * `format` - The format to use for the new control directory.
1141///
1142/// # Returns
1143///
1144/// The newly created control directory, or an error if it could not be created.
1145pub fn create_on_transport(
1146    transport: &Transport,
1147    format: impl AsFormat,
1148) -> Result<
1149    Box<
1150        dyn ControlDir<
1151            Branch = GenericBranch,
1152            Repository = GenericRepository,
1153            WorkingTree = crate::workingtree::GenericWorkingTree,
1154        >,
1155    >,
1156    Error,
1157> {
1158    Python::attach(|py| {
1159        let format = format.as_format().unwrap().0;
1160        Ok(Box::new(GenericControlDir(format.call_method(
1161            py,
1162            "initialize_on_transport",
1163            (transport.as_pyobject(),),
1164            None,
1165        )?))
1166            as Box<
1167                dyn ControlDir<
1168                    Branch = GenericBranch,
1169                    Repository = GenericRepository,
1170                    WorkingTree = crate::workingtree::GenericWorkingTree,
1171                >,
1172            >)
1173    })
1174}
1175
1176/// Find a control directory containing a location specified by a transport.
1177///
1178/// # Parameters
1179///
1180/// * `transport` - The transport to search from.
1181/// * `probers` - Optional list of probers to use to detect control directories.
1182///
1183/// # Returns
1184///
1185/// A tuple containing the control directory and the relative path from the control
1186/// directory to the location specified by the transport, or an error if no control
1187/// directory could be found.
1188pub fn open_containing_from_transport(
1189    transport: &Transport,
1190    probers: Option<&[&dyn PyProber]>,
1191) -> Result<
1192    (
1193        Box<
1194            dyn ControlDir<
1195                Branch = GenericBranch,
1196                Repository = GenericRepository,
1197                WorkingTree = crate::workingtree::GenericWorkingTree,
1198            >,
1199        >,
1200        String,
1201    ),
1202    Error,
1203> {
1204    Python::attach(|py| {
1205        let m = py.import("breezy.controldir")?;
1206        let cd = m.getattr("ControlDir")?;
1207        let kwargs = PyDict::new(py);
1208        if let Some(probers) = probers {
1209            kwargs.set_item(
1210                "probers",
1211                probers.iter().map(|p| p.to_object(py)).collect::<Vec<_>>(),
1212            )?;
1213        }
1214
1215        let (controldir, subpath): (Py<PyAny>, String) = cd
1216            .call_method(
1217                "open_containing_from_transport",
1218                (transport.as_pyobject(),),
1219                Some(&kwargs),
1220            )?
1221            .extract()?;
1222        Ok((
1223            Box::new(GenericControlDir(controldir))
1224                as Box<
1225                    dyn ControlDir<
1226                        Branch = GenericBranch,
1227                        Repository = GenericRepository,
1228                        WorkingTree = crate::workingtree::GenericWorkingTree,
1229                    >,
1230                >,
1231            subpath,
1232        ))
1233    })
1234}
1235
1236/// Open a control directory from a transport.
1237///
1238/// # Parameters
1239///
1240/// * `transport` - The transport to open from.
1241/// * `probers` - Optional list of probers to use to detect control directories.
1242///
1243/// # Returns
1244///
1245/// The opened control directory, or an error if no control directory could be found.
1246pub fn open_from_transport(
1247    transport: &Transport,
1248    probers: Option<&[&dyn PyProber]>,
1249) -> Result<
1250    Box<
1251        dyn ControlDir<
1252            Branch = GenericBranch,
1253            Repository = GenericRepository,
1254            WorkingTree = crate::workingtree::GenericWorkingTree,
1255        >,
1256    >,
1257    Error,
1258> {
1259    Python::attach(|py| {
1260        let m = py.import("breezy.controldir")?;
1261        let cd = m.getattr("ControlDir")?;
1262        let kwargs = PyDict::new(py);
1263        if let Some(probers) = probers {
1264            kwargs.set_item(
1265                "probers",
1266                probers.iter().map(|p| p.to_object(py)).collect::<Vec<_>>(),
1267            )?;
1268        }
1269        let controldir = cd.call_method(
1270            "open_from_transport",
1271            (transport.as_pyobject(),),
1272            Some(&kwargs),
1273        )?;
1274        Ok(Box::new(GenericControlDir(controldir.unbind()))
1275            as Box<
1276                dyn ControlDir<
1277                    Branch = GenericBranch,
1278                    Repository = GenericRepository,
1279                    WorkingTree = crate::workingtree::GenericWorkingTree,
1280                >,
1281            >)
1282    })
1283}
1284
1285/// Trait for types that can be converted to a control directory format.
1286///
1287/// This trait is implemented by types that can be converted to a control directory
1288/// format, like &str and &ControlDirFormat.
1289pub trait AsFormat {
1290    /// Convert to a control directory format.
1291    ///
1292    /// # Returns
1293    ///
1294    /// The control directory format, or None if the conversion failed.
1295    fn as_format(&self) -> Option<ControlDirFormat>;
1296}
1297
1298impl AsFormat for &str {
1299    fn as_format(&self) -> Option<ControlDirFormat> {
1300        Python::attach(|py| {
1301            let m = py.import("breezy.controldir").ok()?;
1302            let cd = m.getattr("format_registry").ok()?;
1303            let format = cd
1304                .call_method1("make_controldir", (self.to_string(),))
1305                .ok()?;
1306            Some(ControlDirFormat(format.unbind()))
1307        })
1308    }
1309}
1310
1311impl AsFormat for &ControlDirFormat {
1312    fn as_format(&self) -> Option<ControlDirFormat> {
1313        Some(Python::attach(|py| ControlDirFormat(self.0.clone_ref(py))))
1314    }
1315}
1316
1317/// Create a branch conveniently (includes creating a repository if needed).
1318///
1319/// # Parameters
1320///
1321/// * `base` - The URL to create the branch at.
1322/// * `force_new_tree` - Whether to force the creation of a new working tree if
1323///   one already exists.
1324/// * `format` - The format to use for the new branch.
1325///
1326/// # Returns
1327///
1328/// The newly created branch, or an error if the branch could not be created.
1329pub fn create_branch_convenience(
1330    base: &url::Url,
1331    force_new_tree: Option<bool>,
1332    format: impl AsFormat,
1333) -> Result<Box<dyn Branch>, Error> {
1334    Python::attach(|py| {
1335        let m = py.import("breezy.controldir")?;
1336        let cd = m.getattr("ControlDir")?;
1337        let format = format.as_format();
1338        let kwargs = PyDict::new(py);
1339        if let Some(force_new_tree) = force_new_tree {
1340            kwargs.set_item("force_new_tree", force_new_tree)?;
1341        }
1342        if let Some(format) = format {
1343            kwargs.set_item("format", format.clone())?;
1344        }
1345        let branch = cd.call_method(
1346            "create_branch_convenience",
1347            (base.to_string(),),
1348            Some(&kwargs),
1349        )?;
1350        Ok(Box::new(GenericBranch::from(branch.unbind())) as Box<dyn Branch>)
1351    })
1352}
1353
1354/// Create a standalone working tree.
1355///
1356/// # Arguments
1357/// * `base` - The base directory for the working tree.
1358/// * `format` - The format of the working tree.
1359pub fn create_standalone_workingtree(
1360    base: &std::path::Path,
1361    format: impl AsFormat,
1362) -> Result<GenericWorkingTree, Error> {
1363    let base = base.to_str().unwrap();
1364    Python::attach(|py| {
1365        let m = py.import("breezy.controldir")?;
1366        let cd = m.getattr("ControlDir")?;
1367        let format = format.as_format();
1368        let wt = cd.call_method(
1369            "create_standalone_workingtree",
1370            (base, format.unwrap_or_default()),
1371            None,
1372        )?;
1373        Ok(GenericWorkingTree(wt.unbind()))
1374    })
1375}
1376
1377/// A generic prober for detecting control directories.
1378///
1379/// This struct wraps a Python prober object and provides access to it through
1380/// the Prober trait.
1381pub struct GenericProber(Py<PyAny>);
1382
1383impl<'py> IntoPyObject<'py> for GenericProber {
1384    type Target = PyAny;
1385    type Output = Bound<'py, Self::Target>;
1386    type Error = std::convert::Infallible;
1387
1388    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
1389        Ok(self.0.into_bound(py))
1390    }
1391}
1392
1393impl<'a, 'py> FromPyObject<'a, 'py> for GenericProber {
1394    type Error = PyErr;
1395
1396    fn extract(obj: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1397        Ok(GenericProber(obj.to_owned().unbind()))
1398    }
1399}
1400
1401impl PyProber for GenericProber {
1402    fn to_object(&self, py: Python) -> Py<PyAny> {
1403        self.0.clone_ref(py)
1404    }
1405}
1406
1407impl GenericProber {
1408    /// Create a new GenericProber from a Python prober object.
1409    ///
1410    /// # Parameters
1411    ///
1412    /// * `obj` - A Python object representing a prober.
1413    ///
1414    /// # Returns
1415    ///
1416    /// A new GenericProber instance.
1417    pub fn new(obj: Py<PyAny>) -> Self {
1418        Self(obj)
1419    }
1420}
1421
1422/// Implementation of Debug for GenericProber.
1423impl std::fmt::Debug for GenericProber {
1424    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1425        f.write_fmt(format_args!("Prober({:?})", self.0))
1426    }
1427}
1428
1429/// Get all available probers.
1430///
1431/// # Returns
1432///
1433/// A list of all available probers.
1434pub fn all_probers() -> Vec<Box<dyn PyProber>> {
1435    Python::attach(|py| -> PyResult<Vec<Box<dyn PyProber>>> {
1436        let m = py.import("breezy.controldir")?;
1437        let cdf = m.getattr("ControlDirFormat")?;
1438        let probers = cdf
1439            .call_method0("all_probers")?
1440            .extract::<Vec<Py<PyAny>>>()?;
1441        Ok(probers
1442            .into_iter()
1443            .map(|p| Box::new(GenericProber::new(p)) as Box<dyn PyProber>)
1444            .collect::<Vec<_>>())
1445    })
1446    .unwrap()
1447}
1448
1449/// A registry of control directory formats.
1450///
1451/// This struct wraps a Python registry of control directory formats,
1452/// which can be used to create control directory formats from names.
1453pub struct ControlDirFormatRegistry(Py<PyAny>);
1454
1455impl ControlDirFormatRegistry {
1456    /// Create a new ControlDirFormatRegistry.
1457    ///
1458    /// # Returns
1459    ///
1460    /// A new ControlDirFormatRegistry instance.
1461    pub fn new() -> Self {
1462        Python::attach(|py| {
1463            let m = py.import("breezy.controldir").unwrap();
1464            let obj = m.getattr("format_registry").unwrap();
1465            ControlDirFormatRegistry(obj.into())
1466        })
1467    }
1468
1469    /// Create a control directory format from a format name.
1470    ///
1471    /// # Parameters
1472    ///
1473    /// * `format` - The name of the format to create.
1474    ///
1475    /// # Returns
1476    ///
1477    /// The control directory format, or None if the format name is not recognized.
1478    pub fn make_controldir(&self, format: &str) -> Option<ControlDirFormat> {
1479        Python::attach(
1480            |py| match self.0.call_method1(py, "make_controldir", (format,)) {
1481                Ok(format) => Some(ControlDirFormat(format)),
1482                Err(e) if e.is_instance_of::<pyo3::exceptions::PyKeyError>(py) => None,
1483                Err(e) => panic!("{}", e),
1484            },
1485        )
1486    }
1487}
1488
1489/// Implementation of Default for ControlDirFormatRegistry.
1490impl Default for ControlDirFormatRegistry {
1491    /// Creates a default ControlDirFormatRegistry.
1492    ///
1493    /// # Returns
1494    ///
1495    /// A new ControlDirFormatRegistry instance.
1496    fn default() -> Self {
1497        ControlDirFormatRegistry::new()
1498    }
1499}
1500
1501lazy_static::lazy_static! {
1502    /// The global control directory format registry.
1503    ///
1504    /// This is a lazily initialized static reference to a `ControlDirFormatRegistry`
1505    /// instance, which can be used to access control directory formats.
1506    pub static ref FORMAT_REGISTRY: ControlDirFormatRegistry = ControlDirFormatRegistry::new();
1507}
1508
1509#[cfg(test)]
1510mod tests {
1511    use super::*;
1512    use crate::workingtree::WorkingTree;
1513
1514    #[test]
1515    fn test_controldir_to_pycontroldir_conversion() {
1516        // Test the pattern from the issue:
1517        // 1. Get a working tree
1518        // 2. Get its controldir as Box<dyn ControlDir>
1519        // 3. Downcast it to use as &dyn PyControlDir
1520
1521        let tmp_dir = tempfile::tempdir().unwrap();
1522        let wt = create_standalone_workingtree(tmp_dir.path(), "2a").unwrap();
1523
1524        // Get controldir as Box<dyn ControlDir>
1525        let controldir = wt.controldir();
1526
1527        // Now try to downcast it to GenericControlDir using as_any()
1528        if let Some(generic_controldir) = controldir.as_any().downcast_ref::<GenericControlDir>() {
1529            // Success! We can now use it as &dyn PyControlDir
1530            let py_controldir: &dyn PyControlDir = generic_controldir;
1531            // Verify we can call PyControlDir methods
1532            Python::attach(|py| {
1533                let _obj = py_controldir.to_object(py);
1534            });
1535        } else {
1536            panic!("Failed to downcast ControlDir to GenericControlDir");
1537        }
1538    }
1539
1540    #[test]
1541    fn test_control_dir_format_registry() {
1542        crate::init();
1543        let registry = ControlDirFormatRegistry::new();
1544        let format = registry.make_controldir("2a").unwrap();
1545        let _ = format.get_format_string();
1546    }
1547
1548    #[test]
1549    fn test_format_registry() {
1550        crate::init();
1551        let format = FORMAT_REGISTRY.make_controldir("2a").unwrap();
1552        let _ = format.get_format_string();
1553    }
1554
1555    #[test]
1556    fn test_all_probers() {
1557        crate::init();
1558        let probers = all_probers();
1559        assert!(!probers.is_empty());
1560    }
1561
1562    #[test]
1563    fn test_open_tree_or_branch() {
1564        crate::init();
1565        let tmp_dir = tempfile::tempdir().unwrap();
1566        create_branch_convenience(
1567            &url::Url::from_directory_path(tmp_dir.path()).unwrap(),
1568            None,
1569            &ControlDirFormat::default(),
1570        )
1571        .unwrap();
1572        let (wt, branch) = open_tree_or_branch(
1573            &url::Url::from_directory_path(tmp_dir.path()).unwrap(),
1574            None,
1575            None,
1576        )
1577        .unwrap();
1578
1579        assert_eq!(
1580            wt.unwrap().basedir().canonicalize().unwrap(),
1581            tmp_dir.path().canonicalize().unwrap()
1582        );
1583        assert_eq!(
1584            branch.get_user_url(),
1585            url::Url::from_directory_path(tmp_dir.path()).unwrap()
1586        );
1587    }
1588
1589    #[test]
1590    fn test_control_dir_format_default() {
1591        crate::init();
1592        let d = ControlDirFormat::default();
1593        d.get_format_string();
1594    }
1595
1596    #[test]
1597    fn test_open() {
1598        crate::init();
1599        let tmp_dir = tempfile::tempdir().unwrap();
1600
1601        let e = open(
1602            &url::Url::from_directory_path(tmp_dir.path()).unwrap(),
1603            None,
1604        )
1605        .unwrap_err();
1606
1607        assert!(matches!(e, Error::NotBranchError(..)),);
1608
1609        let cd = create(
1610            &url::Url::from_directory_path(tmp_dir.path()).unwrap(),
1611            "2a",
1612            None,
1613        )
1614        .unwrap();
1615
1616        let od = open(
1617            &url::Url::from_directory_path(tmp_dir.path()).unwrap(),
1618            None,
1619        )
1620        .unwrap();
1621        assert_eq!(
1622            cd.get_format().get_format_string(),
1623            od.get_format().get_format_string()
1624        );
1625    }
1626
1627    #[test]
1628    fn test_create() {
1629        crate::init();
1630        let tmp_dir = tempfile::tempdir().unwrap();
1631        let cd = create(
1632            &url::Url::from_directory_path(tmp_dir.path()).unwrap(),
1633            "2a",
1634            None,
1635        )
1636        .unwrap();
1637
1638        let od = open(
1639            &url::Url::from_directory_path(tmp_dir.path()).unwrap(),
1640            None,
1641        )
1642        .unwrap();
1643        assert_eq!(
1644            cd.get_format().get_format_string(),
1645            od.get_format().get_format_string()
1646        );
1647    }
1648
1649    #[test]
1650    fn test_create_on_transport() {
1651        crate::init();
1652        let tmp_dir = tempfile::tempdir().unwrap();
1653        let transport = crate::transport::get_transport(
1654            &url::Url::from_directory_path(tmp_dir.path()).unwrap(),
1655            None,
1656        )
1657        .unwrap();
1658        let _cd = create_on_transport(&transport, "2a").unwrap();
1659    }
1660
1661    #[test]
1662    fn test_open_containing_from_transport() {
1663        crate::init();
1664        let tmp_dir = tempfile::tempdir().unwrap();
1665        let transport = crate::transport::get_transport(
1666            &url::Url::from_directory_path(tmp_dir.path()).unwrap(),
1667            None,
1668        )
1669        .unwrap();
1670        let e = open_containing_from_transport(&transport, None).unwrap_err();
1671        assert!(matches!(e, Error::NotBranchError(..)),);
1672    }
1673
1674    #[test]
1675    fn test_open_from_transport() {
1676        crate::init();
1677        let tmp_dir = tempfile::tempdir().unwrap();
1678        let transport = crate::transport::get_transport(
1679            &url::Url::from_directory_path(tmp_dir.path()).unwrap(),
1680            None,
1681        )
1682        .unwrap();
1683        let e = open_from_transport(&transport, None).unwrap_err();
1684        assert!(matches!(e, Error::NotBranchError(..)),);
1685    }
1686
1687    #[test]
1688    fn test_create_standalone_workingtree() {
1689        crate::init();
1690        let tmp_dir = tempfile::tempdir().unwrap();
1691        let wt = create_standalone_workingtree(tmp_dir.path(), "2a").unwrap();
1692
1693        assert_eq!(
1694            wt.basedir().canonicalize().unwrap(),
1695            tmp_dir.path().canonicalize().unwrap()
1696        );
1697    }
1698
1699    #[test]
1700    fn test_create_branch_convenience() {
1701        crate::init();
1702        let tmp_dir = tempfile::tempdir().unwrap();
1703        let branch = create_branch_convenience(
1704            &url::Url::from_directory_path(tmp_dir.path()).unwrap(),
1705            None,
1706            &ControlDirFormat::default(),
1707        )
1708        .unwrap();
1709
1710        assert_eq!(
1711            branch.get_user_url(),
1712            url::Url::from_directory_path(tmp_dir.path()).unwrap()
1713        );
1714    }
1715
1716    #[test]
1717    fn test_create_repository() {
1718        crate::init();
1719        let tmp_dir = tempfile::tempdir().unwrap();
1720        let controldir = create(
1721            &url::Url::from_directory_path(tmp_dir.path()).unwrap(),
1722            &ControlDirFormat::default(),
1723            None,
1724        )
1725        .unwrap();
1726        let _repo = controldir.create_repository(None).unwrap();
1727    }
1728
1729    #[test]
1730    fn test_create_branch() {
1731        crate::init();
1732        let tmp_dir = tempfile::tempdir().unwrap();
1733        let controldir = create(
1734            &url::Url::from_directory_path(tmp_dir.path()).unwrap(),
1735            &ControlDirFormat::default(),
1736            None,
1737        )
1738        .unwrap();
1739        assert!(matches!(
1740            controldir.create_branch(None),
1741            Err(Error::NoRepositoryPresent)
1742        ));
1743        let _repo = controldir.create_repository(None).unwrap();
1744        let _branch = controldir.create_branch(Some("foo")).unwrap();
1745    }
1746
1747    #[test]
1748    fn test_create_workingtree() {
1749        crate::init();
1750        let tmp_dir = tempfile::tempdir().unwrap();
1751        let controldir = create(
1752            &url::Url::from_directory_path(tmp_dir.path()).unwrap(),
1753            &ControlDirFormat::default(),
1754            None,
1755        )
1756        .unwrap();
1757        controldir.create_repository(None).unwrap();
1758        controldir.create_branch(None).unwrap();
1759        let _wt = controldir.create_workingtree().unwrap();
1760    }
1761
1762    #[test]
1763    fn test_branch_names() {
1764        crate::init();
1765        let tmp_dir = tempfile::tempdir().unwrap();
1766        let controldir = create(
1767            &url::Url::from_directory_path(tmp_dir.path()).unwrap(),
1768            &ControlDirFormat::default(),
1769            None,
1770        )
1771        .unwrap();
1772        controldir.create_repository(None).unwrap();
1773        controldir.create_branch(None).unwrap();
1774        assert_eq!(controldir.branch_names().unwrap(), vec!["".to_string()]);
1775    }
1776}