1use 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
14pub trait PyProber: std::any::Any + std::fmt::Debug {
19 fn to_object(&self, py: Python) -> Py<PyAny>;
21}
22
23pub trait Prober: std::fmt::Debug {
28 fn probe_transport(&self, transport: &Transport) -> Result<bool, Error>;
39 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
74pub trait PyControlDir: std::any::Any + std::fmt::Debug {
79 fn to_object(&self, py: Python) -> Py<PyAny>;
81}
82
83pub trait ControlDir: std::fmt::Debug {
89 fn as_any(&self) -> &dyn std::any::Any;
91 type Branch: Branch + ?Sized;
93 type Repository: Repository;
95 type WorkingTree: crate::workingtree::WorkingTree;
97 fn get_user_url(&self) -> url::Url;
103 fn get_format(&self) -> ControlDirFormat;
109 fn user_transport(&self) -> Transport;
115 fn control_transport(&self) -> Transport;
121 fn open_repository(&self) -> Result<Self::Repository, Error>;
127 fn find_repository(&self) -> Result<GenericRepository, Error>;
133 fn cloning_metadir(&self) -> ControlDirFormat;
139 fn create_branch(&self, name: Option<&str>) -> Result<Box<Self::Branch>, Error>;
149 fn create_repository(&self, shared: Option<bool>) -> Result<GenericRepository, Error>;
159 fn open_branch(&self, branch_name: Option<&str>) -> Result<Box<Self::Branch>, Error>;
169 fn create_workingtree(&self) -> crate::Result<GenericWorkingTree>;
175 fn set_branch_reference(&self, branch: &dyn PyBranch, name: Option<&str>) -> crate::Result<()>;
186 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 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 fn has_workingtree(&self) -> bool;
243 fn open_workingtree(&self) -> crate::Result<GenericWorkingTree>;
249 fn branch_names(&self) -> crate::Result<Vec<String>>;
255
256 fn has_branch(&self, name: Option<&str>) -> bool;
266
267 fn create_branch_and_repo(
278 &self,
279 name: Option<&str>,
280 shared: Option<bool>,
281 ) -> Result<Box<Self::Branch>, Error>;
282
283 fn get_branches(&self) -> crate::Result<std::collections::HashMap<String, Box<Self::Branch>>>;
289
290 fn list_branches(&self) -> crate::Result<Vec<String>>;
296
297 fn find_branches(&self, using: Option<bool>) -> crate::Result<Vec<Box<Self::Branch>>>;
307
308 fn get_branch_reference(&self, name: Option<&str>) -> crate::Result<String>;
318
319 fn can_convert_format(&self, format: &ControlDirFormat) -> bool;
329
330 fn check_conversion_target(&self, target_format: &ControlDirFormat) -> crate::Result<()>;
340
341 fn needs_format_conversion(&self, format: Option<&ControlDirFormat>) -> bool;
351
352 fn destroy_branch(&self, name: Option<&str>) -> crate::Result<()>;
362
363 fn destroy_repository(&self) -> crate::Result<()>;
369
370 fn destroy_workingtree(&self) -> crate::Result<()>;
376
377 fn destroy_workingtree_metadata(&self) -> crate::Result<()>;
383
384 fn get_config(&self) -> crate::Result<crate::config::ConfigStack>;
390}
391
392pub 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 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
826pub 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 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 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 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 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 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
991pub 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
1036pub 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}
1083pub 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}
1135pub 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
1176pub 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
1236pub 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
1285pub trait AsFormat {
1290 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
1317pub 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
1354pub 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
1377pub 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 pub fn new(obj: Py<PyAny>) -> Self {
1418 Self(obj)
1419 }
1420}
1421
1422impl 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
1429pub 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
1449pub struct ControlDirFormatRegistry(Py<PyAny>);
1454
1455impl ControlDirFormatRegistry {
1456 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 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
1489impl Default for ControlDirFormatRegistry {
1491 fn default() -> Self {
1497 ControlDirFormatRegistry::new()
1498 }
1499}
1500
1501lazy_static::lazy_static! {
1502 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 let tmp_dir = tempfile::tempdir().unwrap();
1522 let wt = create_standalone_workingtree(tmp_dir.path(), "2a").unwrap();
1523
1524 let controldir = wt.controldir();
1526
1527 if let Some(generic_controldir) = controldir.as_any().downcast_ref::<GenericControlDir>() {
1529 let py_controldir: &dyn PyControlDir = generic_controldir;
1531 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}