1use crate::branch::{Branch, GenericBranch, PyBranch};
6use crate::controldir::{ControlDir, GenericControlDir};
7use crate::error::Error;
8use crate::tree::{MutableTree, PyMutableTree, PyTree, RevisionTree};
9use crate::RevisionId;
10use pyo3::prelude::*;
11use std::path::{Path, PathBuf};
12
13pub trait WorkingTree: MutableTree {
19 fn basedir(&self) -> PathBuf;
25
26 fn controldir(
32 &self,
33 ) -> Box<
34 dyn ControlDir<
35 Branch = GenericBranch,
36 Repository = crate::repository::GenericRepository,
37 WorkingTree = GenericWorkingTree,
38 >,
39 >;
40
41 fn branch(&self) -> GenericBranch;
47
48 fn get_user_url(&self) -> url::Url;
54
55 fn supports_setting_file_ids(&self) -> bool;
61
62 fn smart_add(&self, files: &[&Path]) -> Result<(), Error>;
72
73 fn update(&self, revision_id: Option<&RevisionId>) -> Result<(), Error>;
83
84 fn revert(&self, filenames: Option<&[&Path]>) -> Result<(), Error>;
94
95 fn build_commit(&self) -> CommitBuilder;
101
102 fn basis_tree(&self) -> Result<RevisionTree, Error>;
108
109 fn is_control_filename(&self, path: &Path) -> bool;
122
123 fn revision_tree(&self, revision_id: &RevisionId) -> Result<Box<RevisionTree>, Error>;
133
134 fn abspath(&self, path: &Path) -> Result<PathBuf, Error>;
144
145 fn relpath(&self, path: &Path) -> Result<PathBuf, Error>;
155
156 fn pull(
169 &self,
170 source: &dyn Branch,
171 overwrite: Option<bool>,
172 stop_revision: Option<&RevisionId>,
173 local: Option<bool>,
174 ) -> Result<(), Error>;
175
176 fn merge_from_branch(
187 &self,
188 source: &dyn Branch,
189 to_revision: Option<&RevisionId>,
190 ) -> Result<(), Error>;
191
192 fn safe_relpath_files(
207 &self,
208 file_list: &[&Path],
209 canonicalize: bool,
210 apply_view: bool,
211 ) -> Result<Vec<PathBuf>, Error>;
212
213 fn add_conflicts(&self, conflicts: &[crate::tree::Conflict]) -> Result<(), Error>;
215
216 fn add_parent_tree(
218 &self,
219 parent_id: &RevisionId,
220 parent_tree: &crate::tree::RevisionTree,
221 ) -> Result<(), Error>;
222
223 fn add_parent_tree_id(&self, parent_id: &RevisionId) -> Result<(), Error>;
225
226 fn add_pending_merge(&self, revision_id: &RevisionId) -> Result<(), Error>;
228
229 fn auto_resolve(&self) -> Result<(), Error>;
231
232 fn check_state(&self) -> Result<(), Error>;
234
235 fn get_canonical_path(&self, path: &Path) -> Result<PathBuf, Error>;
237
238 fn get_canonical_paths(&self, paths: &[&Path]) -> Result<Vec<PathBuf>, Error>;
240
241 fn get_config_stack(&self) -> Result<PyObject, Error>;
243
244 fn get_reference_info(&self, path: &Path) -> Result<Option<(String, PathBuf)>, Error>;
246
247 fn get_shelf_manager(&self) -> Result<PyObject, Error>;
249
250 fn ignored_files(&self) -> Result<Vec<PathBuf>, Error>;
252
253 fn is_locked(&self) -> bool;
255
256 fn merge_modified(&self) -> Result<Vec<PathBuf>, Error>;
258
259 fn move_files(&self, from_paths: &[&Path], to_dir: &Path) -> Result<(), Error>;
261
262 fn set_conflicts(&self, conflicts: &[crate::tree::Conflict]) -> Result<(), Error>;
264
265 fn set_last_revision(&self, revision_id: &RevisionId) -> Result<(), Error>;
267
268 fn set_merge_modified(&self, files: &[&Path]) -> Result<(), Error>;
270
271 fn set_pending_merges(&self, revision_ids: &[RevisionId]) -> Result<(), Error>;
273
274 fn set_reference_info(
276 &self,
277 path: &Path,
278 location: &str,
279 file_id: Option<&str>,
280 ) -> Result<(), Error>;
281
282 fn subsume(&self, other: &dyn PyWorkingTree) -> Result<(), Error>;
284
285 fn store_uncommitted(&self) -> Result<String, Error>;
287
288 fn restore_uncommitted(&self) -> Result<(), Error>;
290
291 fn extract(&self, dest: &Path, format: Option<&str>) -> Result<(), Error>;
293
294 fn clone(
296 &self,
297 dest: &Path,
298 revision_id: Option<&RevisionId>,
299 ) -> Result<GenericWorkingTree, Error>;
300
301 fn control_transport(&self) -> Result<crate::transport::Transport, Error>;
303
304 fn control_url(&self) -> url::Url;
306
307 fn copy_content_into(
309 &self,
310 source: &dyn PyTree,
311 revision_id: Option<&RevisionId>,
312 ) -> Result<(), Error>;
313
314 fn flush(&self) -> Result<(), Error>;
316
317 fn requires_rich_root(&self) -> bool;
319
320 fn reset_state(&self, revision_ids: Option<&[RevisionId]>) -> Result<(), Error>;
322
323 fn reference_parent(
325 &self,
326 path: &Path,
327 branch: &dyn Branch,
328 revision_id: Option<&RevisionId>,
329 ) -> Result<(), Error>;
330
331 fn supports_merge_modified(&self) -> bool;
333
334 fn break_lock(&self) -> Result<(), Error>;
336
337 fn get_physical_lock_status(&self) -> Result<bool, Error>;
339}
340
341pub trait PyWorkingTree: PyMutableTree + WorkingTree {}
345
346impl dyn PyWorkingTree {
347 pub fn as_working_tree(&self) -> &dyn WorkingTree {
349 self
350 }
351}
352
353impl<T: ?Sized + PyWorkingTree> WorkingTree for T {
354 fn basedir(&self) -> PathBuf {
355 Python::with_gil(|py| {
356 let path: String = self
357 .to_object(py)
358 .getattr(py, "basedir")
359 .unwrap()
360 .extract(py)
361 .unwrap();
362 PathBuf::from(path)
363 })
364 }
365
366 fn controldir(
367 &self,
368 ) -> Box<
369 dyn ControlDir<
370 Branch = GenericBranch,
371 Repository = crate::repository::GenericRepository,
372 WorkingTree = GenericWorkingTree,
373 >,
374 > {
375 Python::with_gil(|py| {
376 let controldir = self.to_object(py).getattr(py, "controldir").unwrap();
377 Box::new(GenericControlDir::new(controldir))
378 as Box<
379 dyn ControlDir<
380 Branch = GenericBranch,
381 Repository = crate::repository::GenericRepository,
382 WorkingTree = GenericWorkingTree,
383 >,
384 >
385 })
386 }
387
388 fn branch(&self) -> GenericBranch {
389 Python::with_gil(|py| {
390 GenericBranch::from(self.to_object(py).getattr(py, "branch").unwrap())
391 })
392 }
393
394 fn get_user_url(&self) -> url::Url {
395 Python::with_gil(|py| {
396 let url: String = self
397 .to_object(py)
398 .getattr(py, "user_url")
399 .unwrap()
400 .extract(py)
401 .unwrap();
402 url.parse().unwrap()
403 })
404 }
405
406 fn supports_setting_file_ids(&self) -> bool {
407 Python::with_gil(|py| {
408 self.to_object(py)
409 .call_method0(py, "supports_setting_file_ids")
410 .unwrap()
411 .extract(py)
412 .unwrap()
413 })
414 }
415
416 fn smart_add(&self, files: &[&Path]) -> Result<(), Error> {
417 Python::with_gil(|py| {
418 let file_paths: Vec<String> = files
419 .iter()
420 .map(|p| p.to_string_lossy().to_string())
421 .collect();
422 self.to_object(py)
423 .call_method1(py, "smart_add", (file_paths,))?;
424 Ok(())
425 })
426 }
427
428 fn update(&self, revision_id: Option<&RevisionId>) -> Result<(), Error> {
429 Python::with_gil(|py| {
430 self.to_object(py)
431 .call_method1(py, "update", (revision_id.cloned(),))?;
432 Ok(())
433 })
434 }
435
436 fn revert(&self, filenames: Option<&[&Path]>) -> Result<(), Error> {
437 Python::with_gil(|py| {
438 let file_paths = filenames.map(|files| {
439 files
440 .iter()
441 .map(|p| p.to_string_lossy().to_string())
442 .collect::<Vec<String>>()
443 });
444 self.to_object(py)
445 .call_method1(py, "revert", (file_paths,))?;
446 Ok(())
447 })
448 }
449
450 fn build_commit(&self) -> CommitBuilder {
451 Python::with_gil(|py| CommitBuilder::from(GenericWorkingTree(self.to_object(py))))
452 }
453
454 fn basis_tree(&self) -> Result<RevisionTree, Error> {
455 Python::with_gil(|py| {
456 let basis_tree = self.to_object(py).call_method0(py, "basis_tree")?;
457 Ok(RevisionTree(basis_tree))
458 })
459 }
460
461 fn is_control_filename(&self, path: &Path) -> bool {
462 Python::with_gil(|py| {
463 self.to_object(py)
464 .call_method1(
465 py,
466 "is_control_filename",
467 (path.to_string_lossy().as_ref(),),
468 )
469 .unwrap()
470 .extract(py)
471 .unwrap()
472 })
473 }
474
475 fn revision_tree(&self, revision_id: &RevisionId) -> Result<Box<RevisionTree>, Error> {
477 Python::with_gil(|py| {
478 let tree = self.to_object(py).call_method1(
479 py,
480 "revision_tree",
481 (revision_id.clone().into_pyobject(py).unwrap(),),
482 )?;
483 Ok(Box::new(RevisionTree(tree)))
484 })
485 }
486
487 fn abspath(&self, path: &Path) -> Result<PathBuf, Error> {
489 Python::with_gil(|py| {
490 Ok(self
491 .to_object(py)
492 .call_method1(py, "abspath", (path.to_string_lossy().as_ref(),))?
493 .extract(py)?)
494 })
495 }
496
497 fn relpath(&self, path: &Path) -> Result<PathBuf, Error> {
499 Python::with_gil(|py| {
500 Ok(self
501 .to_object(py)
502 .call_method1(py, "relpath", (path.to_string_lossy().as_ref(),))?
503 .extract(py)?)
504 })
505 }
506
507 fn pull(
509 &self,
510 source: &dyn Branch,
511 overwrite: Option<bool>,
512 stop_revision: Option<&RevisionId>,
513 local: Option<bool>,
514 ) -> Result<(), Error> {
515 Python::with_gil(|py| {
516 let kwargs = {
517 let kwargs = pyo3::types::PyDict::new(py);
518 if let Some(overwrite) = overwrite {
519 kwargs.set_item("overwrite", overwrite).unwrap();
520 }
521 if let Some(stop_revision) = stop_revision {
522 kwargs
523 .set_item(
524 "stop_revision",
525 stop_revision.clone().into_pyobject(py).unwrap(),
526 )
527 .unwrap();
528 }
529 if let Some(local) = local {
530 kwargs.set_item("local", local).unwrap();
531 }
532 kwargs
533 };
534 let py_obj =
536 if let Some(generic_branch) = source.as_any().downcast_ref::<GenericBranch>() {
537 generic_branch.to_object(py)
538 } else if let Some(py_branch) = source
539 .as_any()
540 .downcast_ref::<crate::branch::MemoryBranch>()
541 {
542 py_branch.to_object(py)
543 } else {
544 return Err(Error::Other(
545 PyErr::new::<pyo3::exceptions::PyTypeError, _>(
546 "Branch must be a PyBranch implementation for pull operation",
547 ),
548 ));
549 };
550 self.to_object(py)
551 .call_method(py, "pull", (py_obj,), Some(&kwargs))?;
552 Ok(())
553 })
554 }
555
556 fn merge_from_branch(
558 &self,
559 source: &dyn Branch,
560 to_revision: Option<&RevisionId>,
561 ) -> Result<(), Error> {
562 Python::with_gil(|py| {
563 let kwargs = {
564 let kwargs = pyo3::types::PyDict::new(py);
565 if let Some(to_revision) = to_revision {
566 kwargs
567 .set_item(
568 "to_revision",
569 to_revision.clone().into_pyobject(py).unwrap(),
570 )
571 .unwrap();
572 }
573 kwargs
574 };
575 let py_obj =
577 if let Some(generic_branch) = source.as_any().downcast_ref::<GenericBranch>() {
578 generic_branch.to_object(py)
579 } else if let Some(py_branch) = source
580 .as_any()
581 .downcast_ref::<crate::branch::MemoryBranch>()
582 {
583 py_branch.to_object(py)
584 } else {
585 return Err(Error::Other(PyErr::new::<pyo3::exceptions::PyTypeError, _>(
586 "Branch must be a PyBranch implementation for merge_from_branch operation"
587 )));
588 };
589 self.to_object(py)
590 .call_method(py, "merge_from_branch", (py_obj,), Some(&kwargs))?;
591 Ok(())
592 })
593 }
594
595 fn safe_relpath_files(
597 &self,
598 file_list: &[&Path],
599 canonicalize: bool,
600 apply_view: bool,
601 ) -> Result<Vec<PathBuf>, Error> {
602 Python::with_gil(|py| {
603 let result = self.to_object(py).call_method1(
604 py,
605 "safe_relpath_files",
606 (
607 file_list
608 .iter()
609 .map(|x| x.to_string_lossy().to_string())
610 .collect::<Vec<_>>(),
611 canonicalize,
612 apply_view,
613 ),
614 )?;
615 Ok(result.extract(py)?)
616 })
617 }
618
619 fn add_conflicts(&self, conflicts: &[crate::tree::Conflict]) -> Result<(), Error> {
620 Python::with_gil(|py| {
621 let conflicts_py: Vec<PyObject> = conflicts
622 .iter()
623 .map(|c| {
624 let dict = pyo3::types::PyDict::new(py);
625 dict.set_item("path", c.path.to_string_lossy().to_string())
626 .unwrap();
627 dict.set_item("typestring", &c.conflict_type).unwrap();
628 if let Some(ref msg) = c.message {
629 dict.set_item("message", msg).unwrap();
630 }
631 dict.into_any().unbind()
632 })
633 .collect();
634 self.to_object(py)
635 .call_method1(py, "add_conflicts", (conflicts_py,))?;
636 Ok(())
637 })
638 }
639
640 fn add_parent_tree(
641 &self,
642 parent_id: &RevisionId,
643 parent_tree: &crate::tree::RevisionTree,
644 ) -> Result<(), Error> {
645 Python::with_gil(|py| {
646 self.to_object(py).call_method1(
647 py,
648 "add_parent_tree",
649 (
650 parent_id.clone().into_pyobject(py).unwrap(),
651 parent_tree.to_object(py),
652 ),
653 )?;
654 Ok(())
655 })
656 }
657
658 fn add_parent_tree_id(&self, parent_id: &RevisionId) -> Result<(), Error> {
659 Python::with_gil(|py| {
660 self.to_object(py).call_method1(
661 py,
662 "add_parent_tree_id",
663 (parent_id.clone().into_pyobject(py).unwrap(),),
664 )?;
665 Ok(())
666 })
667 }
668
669 fn add_pending_merge(&self, revision_id: &RevisionId) -> Result<(), Error> {
670 Python::with_gil(|py| {
671 self.to_object(py).call_method1(
672 py,
673 "add_pending_merge",
674 (revision_id.clone().into_pyobject(py).unwrap(),),
675 )?;
676 Ok(())
677 })
678 }
679
680 fn auto_resolve(&self) -> Result<(), Error> {
681 Python::with_gil(|py| {
682 self.to_object(py).call_method0(py, "auto_resolve")?;
683 Ok(())
684 })
685 }
686
687 fn check_state(&self) -> Result<(), Error> {
688 Python::with_gil(|py| {
689 self.to_object(py).call_method0(py, "check_state")?;
690 Ok(())
691 })
692 }
693
694 fn get_canonical_path(&self, path: &Path) -> Result<PathBuf, Error> {
695 Python::with_gil(|py| {
696 Ok(self
697 .to_object(py)
698 .call_method1(py, "get_canonical_path", (path.to_string_lossy().as_ref(),))?
699 .extract(py)?)
700 })
701 }
702
703 fn get_canonical_paths(&self, paths: &[&Path]) -> Result<Vec<PathBuf>, Error> {
704 Python::with_gil(|py| {
705 let path_strings: Vec<String> = paths
706 .iter()
707 .map(|p| p.to_string_lossy().to_string())
708 .collect();
709 Ok(self
710 .to_object(py)
711 .call_method1(py, "get_canonical_paths", (path_strings,))?
712 .extract(py)?)
713 })
714 }
715
716 fn get_config_stack(&self) -> Result<PyObject, Error> {
717 Python::with_gil(|py| Ok(self.to_object(py).call_method0(py, "get_config_stack")?))
718 }
719
720 fn get_reference_info(&self, path: &Path) -> Result<Option<(String, PathBuf)>, Error> {
721 Python::with_gil(|py| {
722 let result = self.to_object(py).call_method1(
723 py,
724 "get_reference_info",
725 (path.to_string_lossy().as_ref(),),
726 )?;
727 if result.is_none(py) {
728 Ok(None)
729 } else {
730 let tuple: (String, String) = result.extract(py)?;
731 Ok(Some((tuple.0, PathBuf::from(tuple.1))))
732 }
733 })
734 }
735
736 fn get_shelf_manager(&self) -> Result<PyObject, Error> {
737 Python::with_gil(|py| Ok(self.to_object(py).call_method0(py, "get_shelf_manager")?))
738 }
739
740 fn ignored_files(&self) -> Result<Vec<PathBuf>, Error> {
741 Python::with_gil(|py| {
742 Ok(self
743 .to_object(py)
744 .call_method0(py, "ignored_files")?
745 .extract(py)?)
746 })
747 }
748
749 fn is_locked(&self) -> bool {
750 Python::with_gil(|py| {
751 self.to_object(py)
752 .call_method0(py, "is_locked")
753 .unwrap()
754 .extract(py)
755 .unwrap()
756 })
757 }
758
759 fn merge_modified(&self) -> Result<Vec<PathBuf>, Error> {
760 Python::with_gil(|py| {
761 Ok(self
762 .to_object(py)
763 .call_method0(py, "merge_modified")?
764 .extract(py)?)
765 })
766 }
767
768 fn move_files(&self, from_paths: &[&Path], to_dir: &Path) -> Result<(), Error> {
769 Python::with_gil(|py| {
770 let from_strings: Vec<String> = from_paths
771 .iter()
772 .map(|p| p.to_string_lossy().to_string())
773 .collect();
774 self.to_object(py).call_method1(
775 py,
776 "move",
777 (from_strings, to_dir.to_string_lossy().as_ref()),
778 )?;
779 Ok(())
780 })
781 }
782
783 fn set_conflicts(&self, conflicts: &[crate::tree::Conflict]) -> Result<(), Error> {
784 Python::with_gil(|py| {
785 let conflicts_py: Vec<PyObject> = conflicts
786 .iter()
787 .map(|c| {
788 let dict = pyo3::types::PyDict::new(py);
789 dict.set_item("path", c.path.to_string_lossy().to_string())
790 .unwrap();
791 dict.set_item("typestring", &c.conflict_type).unwrap();
792 if let Some(ref msg) = c.message {
793 dict.set_item("message", msg).unwrap();
794 }
795 dict.into_any().unbind()
796 })
797 .collect();
798 self.to_object(py)
799 .call_method1(py, "set_conflicts", (conflicts_py,))?;
800 Ok(())
801 })
802 }
803
804 fn set_last_revision(&self, revision_id: &RevisionId) -> Result<(), Error> {
805 Python::with_gil(|py| {
806 self.to_object(py).call_method1(
807 py,
808 "set_last_revision",
809 (revision_id.clone().into_pyobject(py).unwrap(),),
810 )?;
811 Ok(())
812 })
813 }
814
815 fn set_merge_modified(&self, files: &[&Path]) -> Result<(), Error> {
816 Python::with_gil(|py| {
817 let file_strings: Vec<String> = files
818 .iter()
819 .map(|p| p.to_string_lossy().to_string())
820 .collect();
821 self.to_object(py)
822 .call_method1(py, "set_merge_modified", (file_strings,))?;
823 Ok(())
824 })
825 }
826
827 fn set_pending_merges(&self, revision_ids: &[RevisionId]) -> Result<(), Error> {
828 Python::with_gil(|py| {
829 let revision_ids_py: Vec<PyObject> = revision_ids
830 .iter()
831 .map(|id| id.clone().into_pyobject(py).unwrap().unbind())
832 .collect();
833 self.to_object(py)
834 .call_method1(py, "set_pending_merges", (revision_ids_py,))?;
835 Ok(())
836 })
837 }
838
839 fn set_reference_info(
840 &self,
841 path: &Path,
842 location: &str,
843 file_id: Option<&str>,
844 ) -> Result<(), Error> {
845 Python::with_gil(|py| {
846 let kwargs = pyo3::types::PyDict::new(py);
847 if let Some(file_id) = file_id {
848 kwargs.set_item("file_id", file_id)?;
849 }
850 self.to_object(py).call_method(
851 py,
852 "set_reference_info",
853 (path.to_string_lossy().as_ref(), location),
854 Some(&kwargs),
855 )?;
856 Ok(())
857 })
858 }
859
860 fn subsume(&self, other: &dyn PyWorkingTree) -> Result<(), Error> {
861 Python::with_gil(|py| {
862 self.to_object(py)
863 .call_method1(py, "subsume", (other.to_object(py),))?;
864 Ok(())
865 })
866 }
867
868 fn store_uncommitted(&self) -> Result<String, Error> {
869 Python::with_gil(|py| {
870 Ok(self
871 .to_object(py)
872 .call_method0(py, "store_uncommitted")?
873 .extract(py)?)
874 })
875 }
876
877 fn restore_uncommitted(&self) -> Result<(), Error> {
878 Python::with_gil(|py| {
879 self.to_object(py).call_method0(py, "restore_uncommitted")?;
880 Ok(())
881 })
882 }
883
884 fn extract(&self, dest: &Path, format: Option<&str>) -> Result<(), Error> {
885 Python::with_gil(|py| {
886 let kwargs = pyo3::types::PyDict::new(py);
887 if let Some(format) = format {
888 kwargs.set_item("format", format)?;
889 }
890 self.to_object(py).call_method(
891 py,
892 "extract",
893 (dest.to_string_lossy().as_ref(),),
894 Some(&kwargs),
895 )?;
896 Ok(())
897 })
898 }
899
900 fn clone(
901 &self,
902 dest: &Path,
903 revision_id: Option<&RevisionId>,
904 ) -> Result<GenericWorkingTree, Error> {
905 Python::with_gil(|py| {
906 let kwargs = pyo3::types::PyDict::new(py);
907 if let Some(revision_id) = revision_id {
908 kwargs.set_item(
909 "revision_id",
910 revision_id.clone().into_pyobject(py).unwrap(),
911 )?;
912 }
913 let result = self.to_object(py).call_method(
914 py,
915 "clone",
916 (dest.to_string_lossy().as_ref(),),
917 Some(&kwargs),
918 )?;
919 Ok(GenericWorkingTree(result))
920 })
921 }
922
923 fn control_transport(&self) -> Result<crate::transport::Transport, Error> {
924 Python::with_gil(|py| {
925 let transport = self.to_object(py).getattr(py, "control_transport")?;
926 Ok(crate::transport::Transport::new(transport))
927 })
928 }
929
930 fn control_url(&self) -> url::Url {
931 Python::with_gil(|py| {
932 let url: String = self
933 .to_object(py)
934 .getattr(py, "control_url")
935 .unwrap()
936 .extract(py)
937 .unwrap();
938 url.parse().unwrap()
939 })
940 }
941
942 fn copy_content_into(
943 &self,
944 source: &dyn PyTree,
945 revision_id: Option<&RevisionId>,
946 ) -> Result<(), Error> {
947 Python::with_gil(|py| {
948 let kwargs = pyo3::types::PyDict::new(py);
949 if let Some(revision_id) = revision_id {
950 kwargs.set_item(
951 "revision_id",
952 revision_id.clone().into_pyobject(py).unwrap(),
953 )?;
954 }
955 self.to_object(py).call_method(
956 py,
957 "copy_content_into",
958 (source.to_object(py),),
959 Some(&kwargs),
960 )?;
961 Ok(())
962 })
963 }
964
965 fn flush(&self) -> Result<(), Error> {
966 Python::with_gil(|py| {
967 self.to_object(py).call_method0(py, "flush")?;
968 Ok(())
969 })
970 }
971
972 fn requires_rich_root(&self) -> bool {
973 Python::with_gil(|py| {
974 self.to_object(py)
975 .call_method0(py, "requires_rich_root")
976 .unwrap()
977 .extract(py)
978 .unwrap()
979 })
980 }
981
982 fn reset_state(&self, revision_ids: Option<&[RevisionId]>) -> Result<(), Error> {
983 Python::with_gil(|py| {
984 let kwargs = pyo3::types::PyDict::new(py);
985 if let Some(revision_ids) = revision_ids {
986 let revision_ids_py: Vec<PyObject> = revision_ids
987 .iter()
988 .map(|id| id.clone().into_pyobject(py).unwrap().unbind())
989 .collect();
990 kwargs.set_item("revision_ids", revision_ids_py)?;
991 }
992 self.to_object(py)
993 .call_method(py, "reset_state", (), Some(&kwargs))?;
994 Ok(())
995 })
996 }
997
998 fn reference_parent(
999 &self,
1000 path: &Path,
1001 branch: &dyn Branch,
1002 revision_id: Option<&RevisionId>,
1003 ) -> Result<(), Error> {
1004 Python::with_gil(|py| {
1005 let kwargs = pyo3::types::PyDict::new(py);
1006 if let Some(revision_id) = revision_id {
1007 kwargs.set_item(
1008 "revision_id",
1009 revision_id.clone().into_pyobject(py).unwrap(),
1010 )?;
1011 }
1012 let py_obj =
1014 if let Some(generic_branch) = branch.as_any().downcast_ref::<GenericBranch>() {
1015 generic_branch.to_object(py)
1016 } else if let Some(py_branch) = branch
1017 .as_any()
1018 .downcast_ref::<crate::branch::MemoryBranch>()
1019 {
1020 py_branch.to_object(py)
1021 } else {
1022 return Err(Error::Other(PyErr::new::<pyo3::exceptions::PyTypeError, _>(
1023 "Branch must be a PyBranch implementation for reference_parent operation"
1024 )));
1025 };
1026 self.to_object(py).call_method(
1027 py,
1028 "reference_parent",
1029 (path.to_string_lossy().as_ref(), py_obj),
1030 Some(&kwargs),
1031 )?;
1032 Ok(())
1033 })
1034 }
1035
1036 fn supports_merge_modified(&self) -> bool {
1037 Python::with_gil(|py| {
1038 self.to_object(py)
1039 .call_method0(py, "supports_merge_modified")
1040 .unwrap()
1041 .extract(py)
1042 .unwrap()
1043 })
1044 }
1045
1046 fn break_lock(&self) -> Result<(), Error> {
1047 Python::with_gil(|py| {
1048 self.to_object(py).call_method0(py, "break_lock")?;
1049 Ok(())
1050 })
1051 }
1052
1053 fn get_physical_lock_status(&self) -> Result<bool, Error> {
1054 Python::with_gil(|py| {
1055 Ok(self
1056 .to_object(py)
1057 .call_method0(py, "get_physical_lock_status")?
1058 .extract(py)?)
1059 })
1060 }
1061}
1062
1063pub struct GenericWorkingTree(pub PyObject);
1069
1070impl crate::tree::PyTree for GenericWorkingTree {
1071 fn to_object(&self, py: Python) -> PyObject {
1072 self.0.clone_ref(py)
1073 }
1074}
1075impl crate::tree::PyMutableTree for GenericWorkingTree {}
1076
1077impl PyWorkingTree for GenericWorkingTree {}
1078
1079impl Clone for GenericWorkingTree {
1080 fn clone(&self) -> Self {
1081 Python::with_gil(|py| GenericWorkingTree(self.0.clone_ref(py)))
1082 }
1083}
1084
1085impl<'py> IntoPyObject<'py> for GenericWorkingTree {
1086 type Target = PyAny;
1087 type Output = Bound<'py, Self::Target>;
1088 type Error = std::convert::Infallible;
1089
1090 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
1091 Ok(self.0.into_bound(py))
1092 }
1093}
1094
1095pub struct CommitBuilder(GenericWorkingTree, Py<pyo3::types::PyDict>);
1100
1101impl From<GenericWorkingTree> for CommitBuilder {
1102 fn from(wt: GenericWorkingTree) -> Self {
1112 Python::with_gil(|py| {
1113 let kwargs = pyo3::types::PyDict::new(py);
1114 CommitBuilder(wt, kwargs.into())
1115 })
1116 }
1117}
1118
1119impl CommitBuilder {
1120 pub fn committer(self, committer: &str) -> Self {
1130 Python::with_gil(|py| {
1131 self.1.bind(py).set_item("committer", committer).unwrap();
1132 });
1133 self
1134 }
1135
1136 pub fn message(self, message: &str) -> Self {
1146 Python::with_gil(|py| {
1147 self.1.bind(py).set_item("message", message).unwrap();
1148 });
1149 self
1150 }
1151
1152 pub fn specific_files(self, specific_files: &[&Path]) -> Self {
1162 let specific_files: Vec<String> = specific_files
1163 .iter()
1164 .map(|x| x.to_string_lossy().to_string())
1165 .collect();
1166 Python::with_gil(|py| {
1167 self.1
1168 .bind(py)
1169 .set_item("specific_files", specific_files)
1170 .unwrap();
1171 });
1172 self
1173 }
1174
1175 pub fn allow_pointless(self, allow_pointless: bool) -> Self {
1185 Python::with_gil(|py| {
1186 self.1
1187 .bind(py)
1188 .set_item("allow_pointless", allow_pointless)
1189 .unwrap();
1190 });
1191 self
1192 }
1193
1194 pub fn reporter(self, reporter: &dyn crate::commit::PyCommitReporter) -> Self {
1204 Python::with_gil(|py| {
1205 self.1
1206 .bind(py)
1207 .set_item("reporter", reporter.to_object(py))
1208 .unwrap();
1209 });
1210 self
1211 }
1212
1213 pub fn timestamp(self, timestamp: f64) -> Self {
1223 Python::with_gil(|py| {
1224 self.1.bind(py).set_item("timestamp", timestamp).unwrap();
1225 });
1226 self
1227 }
1228
1229 pub fn set_revprop(self, key: &str, value: &str) -> Result<Self, Error> {
1243 Python::with_gil(|py| {
1244 if self.1.bind(py).get_item("revprops")?.is_none() {
1246 let new_revprops = pyo3::types::PyDict::new(py);
1247 self.1.bind(py).set_item("revprops", new_revprops)?;
1248 }
1249
1250 let revprops = self.1.bind(py).get_item("revprops")?.ok_or_else(|| {
1252 Error::Other(pyo3::PyErr::new::<pyo3::exceptions::PyAssertionError, _>(
1253 "revprops should exist after setting it",
1254 ))
1255 })?;
1256
1257 let revprops_dict = revprops.downcast::<pyo3::types::PyDict>().map_err(|_| {
1258 Error::Other(pyo3::PyErr::new::<pyo3::exceptions::PyTypeError, _>(
1259 "revprops is not a dictionary",
1260 ))
1261 })?;
1262
1263 revprops_dict.set_item(key, value)?;
1264 Ok(self)
1265 })
1266 }
1267
1268 pub fn commit(self) -> Result<RevisionId, Error> {
1274 Python::with_gil(|py| {
1275 Ok(self
1276 .0
1277 .to_object(py)
1278 .call_method(py, "commit", (), Some(self.1.bind(py)))?
1279 .extract(py)
1280 .unwrap())
1281 })
1282 }
1283}
1284
1285impl GenericWorkingTree {
1286 #[deprecated = "Use ::open instead"]
1298 pub fn open(path: &Path) -> Result<GenericWorkingTree, Error> {
1299 open(path)
1300 }
1301
1302 #[deprecated = "Use ::open_containing instead"]
1315 pub fn open_containing(path: &Path) -> Result<(GenericWorkingTree, PathBuf), Error> {
1316 open_containing(path)
1317 }
1318
1319 #[deprecated = "Use build_commit instead"]
1334 pub fn commit(
1335 &self,
1336 message: &str,
1337 committer: Option<&str>,
1338 timestamp: Option<f64>,
1339 allow_pointless: Option<bool>,
1340 specific_files: Option<&[&Path]>,
1341 ) -> Result<RevisionId, Error> {
1342 let mut builder = self.build_commit().message(message);
1343
1344 if let Some(specific_files) = specific_files {
1345 builder = builder.specific_files(specific_files);
1346 }
1347
1348 if let Some(allow_pointless) = allow_pointless {
1349 builder = builder.allow_pointless(allow_pointless);
1350 }
1351
1352 if let Some(committer) = committer {
1353 builder = builder.committer(committer);
1354 }
1355
1356 if let Some(timestamp) = timestamp {
1357 builder = builder.timestamp(timestamp);
1358 }
1359
1360 builder.commit()
1361 }
1362}
1363
1364pub fn open(path: &Path) -> Result<GenericWorkingTree, Error> {
1374 Python::with_gil(|py| {
1375 let m = py.import("breezy.workingtree")?;
1376 let c = m.getattr("WorkingTree")?;
1377 let wt = c.call_method1("open", (path.to_string_lossy().to_string(),))?;
1378 Ok(GenericWorkingTree(wt.unbind()))
1379 })
1380}
1381
1382pub fn open_containing(path: &Path) -> Result<(GenericWorkingTree, PathBuf), Error> {
1396 Python::with_gil(|py| {
1397 let m = py.import("breezy.workingtree")?;
1398 let c = m.getattr("WorkingTree")?;
1399 let (wt, p): (Bound<PyAny>, String) = c
1400 .call_method1("open_containing", (path.to_string_lossy(),))?
1401 .extract()?;
1402 Ok((GenericWorkingTree(wt.unbind()), PathBuf::from(p)))
1403 })
1404}
1405
1406impl From<PyObject> for GenericWorkingTree {
1408 fn from(obj: PyObject) -> Self {
1418 GenericWorkingTree(obj)
1419 }
1420}