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<Py<PyAny>, Error>;
243
244 fn get_reference_info(&self, path: &Path) -> Result<Option<(String, PathBuf)>, Error>;
246
247 fn get_shelf_manager(&self) -> Result<Py<PyAny>, 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::attach(|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::attach(|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::attach(|py| GenericBranch::from(self.to_object(py).getattr(py, "branch").unwrap()))
390 }
391
392 fn get_user_url(&self) -> url::Url {
393 Python::attach(|py| {
394 let url: String = self
395 .to_object(py)
396 .getattr(py, "user_url")
397 .unwrap()
398 .extract(py)
399 .unwrap();
400 url.parse().unwrap()
401 })
402 }
403
404 fn supports_setting_file_ids(&self) -> bool {
405 Python::attach(|py| {
406 self.to_object(py)
407 .call_method0(py, "supports_setting_file_ids")
408 .unwrap()
409 .extract(py)
410 .unwrap()
411 })
412 }
413
414 fn smart_add(&self, files: &[&Path]) -> Result<(), Error> {
415 Python::attach(|py| {
416 let file_paths: Vec<String> = files
417 .iter()
418 .map(|p| p.to_string_lossy().to_string())
419 .collect();
420 self.to_object(py)
421 .call_method1(py, "smart_add", (file_paths,))?;
422 Ok(())
423 })
424 }
425
426 fn update(&self, revision_id: Option<&RevisionId>) -> Result<(), Error> {
427 Python::attach(|py| {
428 self.to_object(py)
429 .call_method1(py, "update", (revision_id.cloned(),))?;
430 Ok(())
431 })
432 }
433
434 fn revert(&self, filenames: Option<&[&Path]>) -> Result<(), Error> {
435 Python::attach(|py| {
436 let file_paths = filenames.map(|files| {
437 files
438 .iter()
439 .map(|p| p.to_string_lossy().to_string())
440 .collect::<Vec<String>>()
441 });
442 self.to_object(py)
443 .call_method1(py, "revert", (file_paths,))?;
444 Ok(())
445 })
446 }
447
448 fn build_commit(&self) -> CommitBuilder {
449 Python::attach(|py| CommitBuilder::from(GenericWorkingTree(self.to_object(py))))
450 }
451
452 fn basis_tree(&self) -> Result<RevisionTree, Error> {
453 Python::attach(|py| {
454 let basis_tree = self.to_object(py).call_method0(py, "basis_tree")?;
455 Ok(RevisionTree(basis_tree))
456 })
457 }
458
459 fn is_control_filename(&self, path: &Path) -> bool {
460 Python::attach(|py| {
461 self.to_object(py)
462 .call_method1(
463 py,
464 "is_control_filename",
465 (path.to_string_lossy().as_ref(),),
466 )
467 .unwrap()
468 .extract(py)
469 .unwrap()
470 })
471 }
472
473 fn revision_tree(&self, revision_id: &RevisionId) -> Result<Box<RevisionTree>, Error> {
475 Python::attach(|py| {
476 let tree = self.to_object(py).call_method1(
477 py,
478 "revision_tree",
479 (revision_id.clone().into_pyobject(py).unwrap(),),
480 )?;
481 Ok(Box::new(RevisionTree(tree)))
482 })
483 }
484
485 fn abspath(&self, path: &Path) -> Result<PathBuf, Error> {
487 Python::attach(|py| {
488 Ok(self
489 .to_object(py)
490 .call_method1(py, "abspath", (path.to_string_lossy().as_ref(),))?
491 .extract(py)?)
492 })
493 }
494
495 fn relpath(&self, path: &Path) -> Result<PathBuf, Error> {
497 Python::attach(|py| {
498 Ok(self
499 .to_object(py)
500 .call_method1(py, "relpath", (path.to_string_lossy().as_ref(),))?
501 .extract(py)?)
502 })
503 }
504
505 fn pull(
507 &self,
508 source: &dyn Branch,
509 overwrite: Option<bool>,
510 stop_revision: Option<&RevisionId>,
511 local: Option<bool>,
512 ) -> Result<(), Error> {
513 Python::attach(|py| {
514 let kwargs = {
515 let kwargs = pyo3::types::PyDict::new(py);
516 if let Some(overwrite) = overwrite {
517 kwargs.set_item("overwrite", overwrite).unwrap();
518 }
519 if let Some(stop_revision) = stop_revision {
520 kwargs
521 .set_item(
522 "stop_revision",
523 stop_revision.clone().into_pyobject(py).unwrap(),
524 )
525 .unwrap();
526 }
527 if let Some(local) = local {
528 kwargs.set_item("local", local).unwrap();
529 }
530 kwargs
531 };
532 let py_obj =
534 if let Some(generic_branch) = source.as_any().downcast_ref::<GenericBranch>() {
535 generic_branch.to_object(py)
536 } else if let Some(py_branch) = source
537 .as_any()
538 .downcast_ref::<crate::branch::MemoryBranch>()
539 {
540 py_branch.to_object(py)
541 } else {
542 return Err(Error::Other(
543 PyErr::new::<pyo3::exceptions::PyTypeError, _>(
544 "Branch must be a PyBranch implementation for pull operation",
545 ),
546 ));
547 };
548 self.to_object(py)
549 .call_method(py, "pull", (py_obj,), Some(&kwargs))?;
550 Ok(())
551 })
552 }
553
554 fn merge_from_branch(
556 &self,
557 source: &dyn Branch,
558 to_revision: Option<&RevisionId>,
559 ) -> Result<(), Error> {
560 Python::attach(|py| {
561 let kwargs = {
562 let kwargs = pyo3::types::PyDict::new(py);
563 if let Some(to_revision) = to_revision {
564 kwargs
565 .set_item(
566 "to_revision",
567 to_revision.clone().into_pyobject(py).unwrap(),
568 )
569 .unwrap();
570 }
571 kwargs
572 };
573 let py_obj =
575 if let Some(generic_branch) = source.as_any().downcast_ref::<GenericBranch>() {
576 generic_branch.to_object(py)
577 } else if let Some(py_branch) = source
578 .as_any()
579 .downcast_ref::<crate::branch::MemoryBranch>()
580 {
581 py_branch.to_object(py)
582 } else {
583 return Err(Error::Other(PyErr::new::<pyo3::exceptions::PyTypeError, _>(
584 "Branch must be a PyBranch implementation for merge_from_branch operation"
585 )));
586 };
587 self.to_object(py)
588 .call_method(py, "merge_from_branch", (py_obj,), Some(&kwargs))?;
589 Ok(())
590 })
591 }
592
593 fn safe_relpath_files(
595 &self,
596 file_list: &[&Path],
597 canonicalize: bool,
598 apply_view: bool,
599 ) -> Result<Vec<PathBuf>, Error> {
600 Python::attach(|py| {
601 let result = self.to_object(py).call_method1(
602 py,
603 "safe_relpath_files",
604 (
605 file_list
606 .iter()
607 .map(|x| x.to_string_lossy().to_string())
608 .collect::<Vec<_>>(),
609 canonicalize,
610 apply_view,
611 ),
612 )?;
613 Ok(result.extract(py)?)
614 })
615 }
616
617 fn add_conflicts(&self, conflicts: &[crate::tree::Conflict]) -> Result<(), Error> {
618 Python::attach(|py| {
619 let conflicts_py: Vec<Py<PyAny>> = conflicts
620 .iter()
621 .map(|c| {
622 let dict = pyo3::types::PyDict::new(py);
623 dict.set_item("path", c.path.to_string_lossy().to_string())
624 .unwrap();
625 dict.set_item("typestring", &c.conflict_type).unwrap();
626 if let Some(ref msg) = c.message {
627 dict.set_item("message", msg).unwrap();
628 }
629 dict.into_any().unbind()
630 })
631 .collect();
632 self.to_object(py)
633 .call_method1(py, "add_conflicts", (conflicts_py,))?;
634 Ok(())
635 })
636 }
637
638 fn add_parent_tree(
639 &self,
640 parent_id: &RevisionId,
641 parent_tree: &crate::tree::RevisionTree,
642 ) -> Result<(), Error> {
643 Python::attach(|py| {
644 self.to_object(py).call_method1(
645 py,
646 "add_parent_tree",
647 (
648 parent_id.clone().into_pyobject(py).unwrap(),
649 parent_tree.to_object(py),
650 ),
651 )?;
652 Ok(())
653 })
654 }
655
656 fn add_parent_tree_id(&self, parent_id: &RevisionId) -> Result<(), Error> {
657 Python::attach(|py| {
658 self.to_object(py).call_method1(
659 py,
660 "add_parent_tree_id",
661 (parent_id.clone().into_pyobject(py).unwrap(),),
662 )?;
663 Ok(())
664 })
665 }
666
667 fn add_pending_merge(&self, revision_id: &RevisionId) -> Result<(), Error> {
668 Python::attach(|py| {
669 self.to_object(py).call_method1(
670 py,
671 "add_pending_merge",
672 (revision_id.clone().into_pyobject(py).unwrap(),),
673 )?;
674 Ok(())
675 })
676 }
677
678 fn auto_resolve(&self) -> Result<(), Error> {
679 Python::attach(|py| {
680 self.to_object(py).call_method0(py, "auto_resolve")?;
681 Ok(())
682 })
683 }
684
685 fn check_state(&self) -> Result<(), Error> {
686 Python::attach(|py| {
687 self.to_object(py).call_method0(py, "check_state")?;
688 Ok(())
689 })
690 }
691
692 fn get_canonical_path(&self, path: &Path) -> Result<PathBuf, Error> {
693 Python::attach(|py| {
694 Ok(self
695 .to_object(py)
696 .call_method1(py, "get_canonical_path", (path.to_string_lossy().as_ref(),))?
697 .extract(py)?)
698 })
699 }
700
701 fn get_canonical_paths(&self, paths: &[&Path]) -> Result<Vec<PathBuf>, Error> {
702 Python::attach(|py| {
703 let path_strings: Vec<String> = paths
704 .iter()
705 .map(|p| p.to_string_lossy().to_string())
706 .collect();
707 Ok(self
708 .to_object(py)
709 .call_method1(py, "get_canonical_paths", (path_strings,))?
710 .extract(py)?)
711 })
712 }
713
714 fn get_config_stack(&self) -> Result<Py<PyAny>, Error> {
715 Python::attach(|py| Ok(self.to_object(py).call_method0(py, "get_config_stack")?))
716 }
717
718 fn get_reference_info(&self, path: &Path) -> Result<Option<(String, PathBuf)>, Error> {
719 Python::attach(|py| {
720 let result = self.to_object(py).call_method1(
721 py,
722 "get_reference_info",
723 (path.to_string_lossy().as_ref(),),
724 )?;
725 if result.is_none(py) {
726 Ok(None)
727 } else {
728 let tuple: (String, String) = result.extract(py)?;
729 Ok(Some((tuple.0, PathBuf::from(tuple.1))))
730 }
731 })
732 }
733
734 fn get_shelf_manager(&self) -> Result<Py<PyAny>, Error> {
735 Python::attach(|py| Ok(self.to_object(py).call_method0(py, "get_shelf_manager")?))
736 }
737
738 fn ignored_files(&self) -> Result<Vec<PathBuf>, Error> {
739 Python::attach(|py| {
740 Ok(self
741 .to_object(py)
742 .call_method0(py, "ignored_files")?
743 .extract(py)?)
744 })
745 }
746
747 fn is_locked(&self) -> bool {
748 Python::attach(|py| {
749 self.to_object(py)
750 .call_method0(py, "is_locked")
751 .unwrap()
752 .extract(py)
753 .unwrap()
754 })
755 }
756
757 fn merge_modified(&self) -> Result<Vec<PathBuf>, Error> {
758 Python::attach(|py| {
759 Ok(self
760 .to_object(py)
761 .call_method0(py, "merge_modified")?
762 .extract(py)?)
763 })
764 }
765
766 fn move_files(&self, from_paths: &[&Path], to_dir: &Path) -> Result<(), Error> {
767 Python::attach(|py| {
768 let from_strings: Vec<String> = from_paths
769 .iter()
770 .map(|p| p.to_string_lossy().to_string())
771 .collect();
772 self.to_object(py).call_method1(
773 py,
774 "move",
775 (from_strings, to_dir.to_string_lossy().as_ref()),
776 )?;
777 Ok(())
778 })
779 }
780
781 fn set_conflicts(&self, conflicts: &[crate::tree::Conflict]) -> Result<(), Error> {
782 Python::attach(|py| {
783 let conflicts_py: Vec<Py<PyAny>> = conflicts
784 .iter()
785 .map(|c| {
786 let dict = pyo3::types::PyDict::new(py);
787 dict.set_item("path", c.path.to_string_lossy().to_string())
788 .unwrap();
789 dict.set_item("typestring", &c.conflict_type).unwrap();
790 if let Some(ref msg) = c.message {
791 dict.set_item("message", msg).unwrap();
792 }
793 dict.into_any().unbind()
794 })
795 .collect();
796 self.to_object(py)
797 .call_method1(py, "set_conflicts", (conflicts_py,))?;
798 Ok(())
799 })
800 }
801
802 fn set_last_revision(&self, revision_id: &RevisionId) -> Result<(), Error> {
803 Python::attach(|py| {
804 self.to_object(py).call_method1(
805 py,
806 "set_last_revision",
807 (revision_id.clone().into_pyobject(py).unwrap(),),
808 )?;
809 Ok(())
810 })
811 }
812
813 fn set_merge_modified(&self, files: &[&Path]) -> Result<(), Error> {
814 Python::attach(|py| {
815 let file_strings: Vec<String> = files
816 .iter()
817 .map(|p| p.to_string_lossy().to_string())
818 .collect();
819 self.to_object(py)
820 .call_method1(py, "set_merge_modified", (file_strings,))?;
821 Ok(())
822 })
823 }
824
825 fn set_pending_merges(&self, revision_ids: &[RevisionId]) -> Result<(), Error> {
826 Python::attach(|py| {
827 let revision_ids_py: Vec<Py<PyAny>> = revision_ids
828 .iter()
829 .map(|id| id.clone().into_pyobject(py).unwrap().unbind())
830 .collect();
831 self.to_object(py)
832 .call_method1(py, "set_pending_merges", (revision_ids_py,))?;
833 Ok(())
834 })
835 }
836
837 fn set_reference_info(
838 &self,
839 path: &Path,
840 location: &str,
841 file_id: Option<&str>,
842 ) -> Result<(), Error> {
843 Python::attach(|py| {
844 let kwargs = pyo3::types::PyDict::new(py);
845 if let Some(file_id) = file_id {
846 kwargs.set_item("file_id", file_id)?;
847 }
848 self.to_object(py).call_method(
849 py,
850 "set_reference_info",
851 (path.to_string_lossy().as_ref(), location),
852 Some(&kwargs),
853 )?;
854 Ok(())
855 })
856 }
857
858 fn subsume(&self, other: &dyn PyWorkingTree) -> Result<(), Error> {
859 Python::attach(|py| {
860 self.to_object(py)
861 .call_method1(py, "subsume", (other.to_object(py),))?;
862 Ok(())
863 })
864 }
865
866 fn store_uncommitted(&self) -> Result<String, Error> {
867 Python::attach(|py| {
868 Ok(self
869 .to_object(py)
870 .call_method0(py, "store_uncommitted")?
871 .extract(py)?)
872 })
873 }
874
875 fn restore_uncommitted(&self) -> Result<(), Error> {
876 Python::attach(|py| {
877 self.to_object(py).call_method0(py, "restore_uncommitted")?;
878 Ok(())
879 })
880 }
881
882 fn extract(&self, dest: &Path, format: Option<&str>) -> Result<(), Error> {
883 Python::attach(|py| {
884 let kwargs = pyo3::types::PyDict::new(py);
885 if let Some(format) = format {
886 kwargs.set_item("format", format)?;
887 }
888 self.to_object(py).call_method(
889 py,
890 "extract",
891 (dest.to_string_lossy().as_ref(),),
892 Some(&kwargs),
893 )?;
894 Ok(())
895 })
896 }
897
898 fn clone(
899 &self,
900 dest: &Path,
901 revision_id: Option<&RevisionId>,
902 ) -> Result<GenericWorkingTree, Error> {
903 Python::attach(|py| {
904 let kwargs = pyo3::types::PyDict::new(py);
905 if let Some(revision_id) = revision_id {
906 kwargs.set_item(
907 "revision_id",
908 revision_id.clone().into_pyobject(py).unwrap(),
909 )?;
910 }
911 let result = self.to_object(py).call_method(
912 py,
913 "clone",
914 (dest.to_string_lossy().as_ref(),),
915 Some(&kwargs),
916 )?;
917 Ok(GenericWorkingTree(result))
918 })
919 }
920
921 fn control_transport(&self) -> Result<crate::transport::Transport, Error> {
922 Python::attach(|py| {
923 let transport = self.to_object(py).getattr(py, "control_transport")?;
924 Ok(crate::transport::Transport::new(transport))
925 })
926 }
927
928 fn control_url(&self) -> url::Url {
929 Python::attach(|py| {
930 let url: String = self
931 .to_object(py)
932 .getattr(py, "control_url")
933 .unwrap()
934 .extract(py)
935 .unwrap();
936 url.parse().unwrap()
937 })
938 }
939
940 fn copy_content_into(
941 &self,
942 source: &dyn PyTree,
943 revision_id: Option<&RevisionId>,
944 ) -> Result<(), Error> {
945 Python::attach(|py| {
946 let kwargs = pyo3::types::PyDict::new(py);
947 if let Some(revision_id) = revision_id {
948 kwargs.set_item(
949 "revision_id",
950 revision_id.clone().into_pyobject(py).unwrap(),
951 )?;
952 }
953 self.to_object(py).call_method(
954 py,
955 "copy_content_into",
956 (source.to_object(py),),
957 Some(&kwargs),
958 )?;
959 Ok(())
960 })
961 }
962
963 fn flush(&self) -> Result<(), Error> {
964 Python::attach(|py| {
965 self.to_object(py).call_method0(py, "flush")?;
966 Ok(())
967 })
968 }
969
970 fn requires_rich_root(&self) -> bool {
971 Python::attach(|py| {
972 self.to_object(py)
973 .call_method0(py, "requires_rich_root")
974 .unwrap()
975 .extract(py)
976 .unwrap()
977 })
978 }
979
980 fn reset_state(&self, revision_ids: Option<&[RevisionId]>) -> Result<(), Error> {
981 Python::attach(|py| {
982 let kwargs = pyo3::types::PyDict::new(py);
983 if let Some(revision_ids) = revision_ids {
984 let revision_ids_py: Vec<Py<PyAny>> = revision_ids
985 .iter()
986 .map(|id| id.clone().into_pyobject(py).unwrap().unbind())
987 .collect();
988 kwargs.set_item("revision_ids", revision_ids_py)?;
989 }
990 self.to_object(py)
991 .call_method(py, "reset_state", (), Some(&kwargs))?;
992 Ok(())
993 })
994 }
995
996 fn reference_parent(
997 &self,
998 path: &Path,
999 branch: &dyn Branch,
1000 revision_id: Option<&RevisionId>,
1001 ) -> Result<(), Error> {
1002 Python::attach(|py| {
1003 let kwargs = pyo3::types::PyDict::new(py);
1004 if let Some(revision_id) = revision_id {
1005 kwargs.set_item(
1006 "revision_id",
1007 revision_id.clone().into_pyobject(py).unwrap(),
1008 )?;
1009 }
1010 let py_obj =
1012 if let Some(generic_branch) = branch.as_any().downcast_ref::<GenericBranch>() {
1013 generic_branch.to_object(py)
1014 } else if let Some(py_branch) = branch
1015 .as_any()
1016 .downcast_ref::<crate::branch::MemoryBranch>()
1017 {
1018 py_branch.to_object(py)
1019 } else {
1020 return Err(Error::Other(PyErr::new::<pyo3::exceptions::PyTypeError, _>(
1021 "Branch must be a PyBranch implementation for reference_parent operation"
1022 )));
1023 };
1024 self.to_object(py).call_method(
1025 py,
1026 "reference_parent",
1027 (path.to_string_lossy().as_ref(), py_obj),
1028 Some(&kwargs),
1029 )?;
1030 Ok(())
1031 })
1032 }
1033
1034 fn supports_merge_modified(&self) -> bool {
1035 Python::attach(|py| {
1036 self.to_object(py)
1037 .call_method0(py, "supports_merge_modified")
1038 .unwrap()
1039 .extract(py)
1040 .unwrap()
1041 })
1042 }
1043
1044 fn break_lock(&self) -> Result<(), Error> {
1045 Python::attach(|py| {
1046 self.to_object(py).call_method0(py, "break_lock")?;
1047 Ok(())
1048 })
1049 }
1050
1051 fn get_physical_lock_status(&self) -> Result<bool, Error> {
1052 Python::attach(|py| {
1053 Ok(self
1054 .to_object(py)
1055 .call_method0(py, "get_physical_lock_status")?
1056 .extract(py)?)
1057 })
1058 }
1059}
1060
1061pub struct GenericWorkingTree(pub Py<PyAny>);
1067
1068impl crate::tree::PyTree for GenericWorkingTree {
1069 fn to_object(&self, py: Python) -> Py<PyAny> {
1070 self.0.clone_ref(py)
1071 }
1072}
1073impl crate::tree::PyMutableTree for GenericWorkingTree {}
1074
1075impl PyWorkingTree for GenericWorkingTree {}
1076
1077impl Clone for GenericWorkingTree {
1078 fn clone(&self) -> Self {
1079 Python::attach(|py| GenericWorkingTree(self.0.clone_ref(py)))
1080 }
1081}
1082
1083impl<'py> IntoPyObject<'py> for GenericWorkingTree {
1084 type Target = PyAny;
1085 type Output = Bound<'py, Self::Target>;
1086 type Error = std::convert::Infallible;
1087
1088 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
1089 Ok(self.0.into_bound(py))
1090 }
1091}
1092
1093pub struct CommitBuilder(GenericWorkingTree, Py<pyo3::types::PyDict>);
1098
1099impl From<GenericWorkingTree> for CommitBuilder {
1100 fn from(wt: GenericWorkingTree) -> Self {
1110 Python::attach(|py| {
1111 let kwargs = pyo3::types::PyDict::new(py);
1112 CommitBuilder(wt, kwargs.into())
1113 })
1114 }
1115}
1116
1117impl CommitBuilder {
1118 pub fn committer(self, committer: &str) -> Self {
1128 Python::attach(|py| {
1129 self.1.bind(py).set_item("committer", committer).unwrap();
1130 });
1131 self
1132 }
1133
1134 pub fn message(self, message: &str) -> Self {
1144 Python::attach(|py| {
1145 self.1.bind(py).set_item("message", message).unwrap();
1146 });
1147 self
1148 }
1149
1150 pub fn specific_files(self, specific_files: &[&Path]) -> Self {
1160 let specific_files: Vec<String> = specific_files
1161 .iter()
1162 .map(|x| x.to_string_lossy().to_string())
1163 .collect();
1164 Python::attach(|py| {
1165 self.1
1166 .bind(py)
1167 .set_item("specific_files", specific_files)
1168 .unwrap();
1169 });
1170 self
1171 }
1172
1173 pub fn allow_pointless(self, allow_pointless: bool) -> Self {
1183 Python::attach(|py| {
1184 self.1
1185 .bind(py)
1186 .set_item("allow_pointless", allow_pointless)
1187 .unwrap();
1188 });
1189 self
1190 }
1191
1192 pub fn reporter(self, reporter: &dyn crate::commit::PyCommitReporter) -> Self {
1202 Python::attach(|py| {
1203 self.1
1204 .bind(py)
1205 .set_item("reporter", reporter.to_object(py))
1206 .unwrap();
1207 });
1208 self
1209 }
1210
1211 pub fn timestamp(self, timestamp: f64) -> Self {
1221 Python::attach(|py| {
1222 self.1.bind(py).set_item("timestamp", timestamp).unwrap();
1223 });
1224 self
1225 }
1226
1227 pub fn set_revprop(self, key: &str, value: &str) -> Result<Self, Error> {
1241 Python::attach(|py| {
1242 if self.1.bind(py).get_item("revprops")?.is_none() {
1244 let new_revprops = pyo3::types::PyDict::new(py);
1245 self.1.bind(py).set_item("revprops", new_revprops)?;
1246 }
1247
1248 let revprops = self.1.bind(py).get_item("revprops")?.ok_or_else(|| {
1250 Error::Other(pyo3::PyErr::new::<pyo3::exceptions::PyAssertionError, _>(
1251 "revprops should exist after setting it",
1252 ))
1253 })?;
1254
1255 let revprops_dict = revprops.cast::<pyo3::types::PyDict>().map_err(|_| {
1256 Error::Other(pyo3::PyErr::new::<pyo3::exceptions::PyTypeError, _>(
1257 "revprops is not a dictionary",
1258 ))
1259 })?;
1260
1261 revprops_dict.set_item(key, value)?;
1262 Ok(self)
1263 })
1264 }
1265
1266 pub fn commit(self) -> Result<RevisionId, Error> {
1272 Python::attach(|py| {
1273 Ok(self
1274 .0
1275 .to_object(py)
1276 .call_method(py, "commit", (), Some(self.1.bind(py)))?
1277 .extract(py)
1278 .unwrap())
1279 })
1280 }
1281}
1282
1283impl GenericWorkingTree {
1284 #[deprecated = "Use ::open instead"]
1296 pub fn open(path: &Path) -> Result<GenericWorkingTree, Error> {
1297 open(path)
1298 }
1299
1300 #[deprecated = "Use ::open_containing instead"]
1313 pub fn open_containing(path: &Path) -> Result<(GenericWorkingTree, PathBuf), Error> {
1314 open_containing(path)
1315 }
1316
1317 #[deprecated = "Use build_commit instead"]
1332 pub fn commit(
1333 &self,
1334 message: &str,
1335 committer: Option<&str>,
1336 timestamp: Option<f64>,
1337 allow_pointless: Option<bool>,
1338 specific_files: Option<&[&Path]>,
1339 ) -> Result<RevisionId, Error> {
1340 let mut builder = self.build_commit().message(message);
1341
1342 if let Some(specific_files) = specific_files {
1343 builder = builder.specific_files(specific_files);
1344 }
1345
1346 if let Some(allow_pointless) = allow_pointless {
1347 builder = builder.allow_pointless(allow_pointless);
1348 }
1349
1350 if let Some(committer) = committer {
1351 builder = builder.committer(committer);
1352 }
1353
1354 if let Some(timestamp) = timestamp {
1355 builder = builder.timestamp(timestamp);
1356 }
1357
1358 builder.commit()
1359 }
1360}
1361
1362pub fn open(path: &Path) -> Result<GenericWorkingTree, Error> {
1372 Python::attach(|py| {
1373 let m = py.import("breezy.workingtree")?;
1374 let c = m.getattr("WorkingTree")?;
1375 let wt = c.call_method1("open", (path.to_string_lossy().to_string(),))?;
1376 Ok(GenericWorkingTree(wt.unbind()))
1377 })
1378}
1379
1380pub fn open_containing(path: &Path) -> Result<(GenericWorkingTree, PathBuf), Error> {
1394 Python::attach(|py| {
1395 let m = py.import("breezy.workingtree")?;
1396 let c = m.getattr("WorkingTree")?;
1397 let (wt, p): (Bound<PyAny>, String) = c
1398 .call_method1("open_containing", (path.to_string_lossy(),))?
1399 .extract()?;
1400 Ok((GenericWorkingTree(wt.unbind()), PathBuf::from(p)))
1401 })
1402}
1403
1404impl From<Py<PyAny>> for GenericWorkingTree {
1406 fn from(obj: Py<PyAny>) -> Self {
1416 GenericWorkingTree(obj)
1417 }
1418}