1use crate::error::Error;
3use crate::lock::Lock;
4use crate::revisionid::RevisionId;
5use pyo3::intern;
6use pyo3::prelude::*;
7
8pub type Path = std::path::Path;
10pub type PathBuf = std::path::PathBuf;
12
13fn convert_python_stat_to_metadata(
29 py_stat: &Py<PyAny>,
30) -> Result<Option<std::fs::Metadata>, Error> {
31 Python::attach(|py| {
32 let stat_obj = py_stat.bind(py);
33
34 let _st_mode: u32 = stat_obj.getattr("st_mode")?.extract()?;
36 let _st_size: u64 = stat_obj.getattr("st_size")?.extract()?;
37 let _st_mtime: f64 = stat_obj.getattr("st_mtime")?.extract()?;
38
39 Ok(None)
42 })
43}
44
45#[derive(Debug)]
47pub struct WalkdirResult {
48 pub relpath: PathBuf,
50 pub kind: Kind,
52 pub stat: Option<std::fs::Metadata>,
54 pub versioned: bool,
56}
57
58#[derive(Debug)]
60pub struct PathContentSummary {
61 pub kind: Kind,
63 pub size: Option<u64>,
65 pub executable: Option<bool>,
67 pub sha1: Option<String>,
69 pub target: Option<String>,
71}
72
73#[derive(Debug)]
75pub struct SearchRule {
76 pub pattern: String,
78 pub rule_type: SearchRuleType,
80}
81
82#[derive(Debug)]
84pub enum SearchRuleType {
85 Include,
87 Exclude,
89}
90
91#[derive(Debug)]
93pub struct Conflict {
94 pub path: PathBuf,
96 pub conflict_type: String,
98 pub message: Option<String>,
100}
101
102impl<'a, 'py> FromPyObject<'a, 'py> for Conflict {
103 type Error = PyErr;
104
105 fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
106 let path: String = ob.getattr("path")?.extract()?;
107 let conflict_type: String = ob.getattr("typestring")?.extract()?;
108 let message: Option<String> = ob.getattr("message").ok().and_then(|m| m.extract().ok());
109
110 Ok(Conflict {
111 path: PathBuf::from(path),
112 conflict_type,
113 message,
114 })
115 }
116}
117
118#[derive(Debug)]
120pub struct TreeReference {
121 pub path: PathBuf,
123 pub kind: Kind,
125 pub reference_revision: Option<RevisionId>,
127}
128
129#[derive(Debug)]
131pub struct InventoryDelta {
132 pub old_path: Option<PathBuf>,
134 pub new_path: Option<PathBuf>,
136 pub file_id: String,
138 pub entry: Option<TreeEntry>,
140}
141
142#[derive(Debug, PartialEq, Clone, Eq)]
143pub enum Kind {
145 File,
147 Directory,
149 Symlink,
151 TreeReference,
153}
154
155impl Kind {
156 pub fn marker(&self) -> &'static str {
158 match self {
159 Kind::File => "",
160 Kind::Directory => "/",
161 Kind::Symlink => "@",
162 Kind::TreeReference => "+",
163 }
164 }
165}
166
167impl std::fmt::Display for Kind {
168 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
169 match self {
170 Kind::File => write!(f, "file"),
171 Kind::Directory => write!(f, "directory"),
172 Kind::Symlink => write!(f, "symlink"),
173 Kind::TreeReference => write!(f, "tree-reference"),
174 }
175 }
176}
177
178impl std::str::FromStr for Kind {
179 type Err = String;
180
181 fn from_str(s: &str) -> Result<Self, Self::Err> {
182 match s {
183 "file" => Ok(Kind::File),
184 "directory" => Ok(Kind::Directory),
185 "symlink" => Ok(Kind::Symlink),
186 "tree-reference" => Ok(Kind::TreeReference),
187 n => Err(format!("Invalid kind: {}", n)),
188 }
189 }
190}
191
192impl<'py> pyo3::IntoPyObject<'py> for Kind {
193 type Target = pyo3::PyAny;
194 type Output = pyo3::Bound<'py, Self::Target>;
195 type Error = std::convert::Infallible;
196
197 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
198 let s = match self {
199 Kind::File => "file",
200 Kind::Directory => "directory",
201 Kind::Symlink => "symlink",
202 Kind::TreeReference => "tree-reference",
203 };
204 Ok(pyo3::types::PyString::new(py, s).into_any())
205 }
206}
207
208impl<'a, 'py> pyo3::FromPyObject<'a, 'py> for Kind {
209 type Error = PyErr;
210
211 fn extract(ob: Borrowed<'a, 'py, pyo3::PyAny>) -> PyResult<Self> {
212 let s: String = ob.extract()?;
213 match s.as_str() {
214 "file" => Ok(Kind::File),
215 "directory" => Ok(Kind::Directory),
216 "symlink" => Ok(Kind::Symlink),
217 "tree-reference" => Ok(Kind::TreeReference),
218 _ => Err(pyo3::exceptions::PyValueError::new_err(format!(
219 "Invalid kind: {}",
220 s
221 ))),
222 }
223 }
224}
225
226#[derive(Debug)]
228pub enum TreeEntry {
229 File {
231 executable: bool,
233 kind: Kind,
235 revision: Option<RevisionId>,
237 size: u64,
239 },
240 Directory {
242 revision: Option<RevisionId>,
244 },
245 Symlink {
247 revision: Option<RevisionId>,
249 symlink_target: String,
251 },
252 TreeReference {
254 revision: Option<RevisionId>,
256 reference_revision: RevisionId,
258 },
259}
260
261impl<'a, 'py> FromPyObject<'a, 'py> for TreeEntry {
262 type Error = PyErr;
263
264 fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
265 let kind: String = ob.getattr("kind")?.extract()?;
266 match kind.as_str() {
267 "file" => {
268 let executable: bool = ob.getattr("executable")?.extract()?;
269 let kind: Kind = ob.getattr("kind")?.extract()?;
270 let size: u64 = ob.getattr("size")?.extract()?;
271 let revision: Option<RevisionId> = ob.getattr("revision")?.extract()?;
272 Ok(TreeEntry::File {
273 executable,
274 kind,
275 size,
276 revision,
277 })
278 }
279 "directory" => {
280 let revision: Option<RevisionId> = ob.getattr("revision")?.extract()?;
281 Ok(TreeEntry::Directory { revision })
282 }
283 "symlink" => {
284 let revision: Option<RevisionId> = ob.getattr("revision")?.extract()?;
285 let symlink_target: String = ob.getattr("symlink_target")?.extract()?;
286 Ok(TreeEntry::Symlink {
287 revision,
288 symlink_target,
289 })
290 }
291 "tree-reference" => {
292 let revision: Option<RevisionId> = ob.getattr("revision")?.extract()?;
293 let reference_revision: RevisionId = ob.getattr("reference_revision")?.extract()?;
294 Ok(TreeEntry::TreeReference {
295 revision,
296 reference_revision,
297 })
298 }
299 kind => panic!("Invalid kind: {}", kind),
300 }
301 }
302}
303
304impl<'py> IntoPyObject<'py> for TreeEntry {
305 type Target = PyAny;
306 type Output = Bound<'py, Self::Target>;
307 type Error = std::convert::Infallible;
308
309 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
310 let dict = pyo3::types::PyDict::new(py);
311 match self {
312 TreeEntry::File {
313 executable,
314 kind: _,
315 revision,
316 size,
317 } => {
318 dict.set_item("kind", "file").unwrap();
319 dict.set_item("executable", executable).unwrap();
320 dict.set_item("size", size).unwrap();
321 dict.set_item("revision", revision).unwrap();
322 }
323 TreeEntry::Directory { revision } => {
324 dict.set_item("kind", "directory").unwrap();
325 dict.set_item("revision", revision).unwrap();
326 }
327 TreeEntry::Symlink {
328 revision,
329 symlink_target,
330 } => {
331 dict.set_item("kind", "symlink").unwrap();
332 dict.set_item("revision", revision).unwrap();
333 dict.set_item("symlink_target", symlink_target).unwrap();
334 }
335 TreeEntry::TreeReference {
336 revision,
337 reference_revision,
338 } => {
339 dict.set_item("kind", "tree-reference").unwrap();
340 dict.set_item("revision", revision).unwrap();
341 dict.set_item("reference_revision", reference_revision)
342 .unwrap();
343 }
344 }
345 Ok(dict.into_any())
346 }
347}
348
349pub trait Tree {
354 fn get_tag_dict(&self) -> Result<std::collections::HashMap<String, RevisionId>, Error>;
356 fn get_file(&self, path: &Path) -> Result<Box<dyn std::io::Read>, Error>;
358 fn get_file_text(&self, path: &Path) -> Result<Vec<u8>, Error>;
360 fn get_file_lines(&self, path: &Path) -> Result<Vec<Vec<u8>>, Error>;
362 fn lock_read(&self) -> Result<Lock, Error>;
364
365 fn has_filename(&self, path: &Path) -> bool;
367
368 fn get_symlink_target(&self, path: &Path) -> Result<PathBuf, Error>;
370
371 fn get_parent_ids(&self) -> Result<Vec<RevisionId>, Error>;
373 fn is_ignored(&self, path: &Path) -> Option<String>;
375 fn kind(&self, path: &Path) -> Result<Kind, Error>;
377 fn is_versioned(&self, path: &Path) -> bool;
379
380 fn iter_changes(
388 &self,
389 other: &dyn PyTree,
390 specific_files: Option<&[&Path]>,
391 want_unversioned: Option<bool>,
392 require_versioned: Option<bool>,
393 ) -> Result<Box<dyn Iterator<Item = Result<TreeChange, Error>>>, Error>;
394
395 fn has_versioned_directories(&self) -> bool;
397
398 fn preview_transform(&self) -> Result<crate::transform::TreeTransform, Error>;
400
401 fn list_files(
409 &self,
410 include_root: Option<bool>,
411 from_dir: Option<&Path>,
412 recursive: Option<bool>,
413 recurse_nested: Option<bool>,
414 ) -> Result<Box<dyn Iterator<Item = Result<(PathBuf, bool, Kind, TreeEntry), Error>>>, Error>;
415
416 fn iter_child_entries(
421 &self,
422 path: &std::path::Path,
423 ) -> Result<Box<dyn Iterator<Item = Result<(PathBuf, Kind, TreeEntry), Error>>>, Error>;
424
425 fn get_file_size(&self, path: &Path) -> Result<u64, Error>;
427
428 fn get_file_sha1(
430 &self,
431 path: &Path,
432 stat_value: Option<&std::fs::Metadata>,
433 ) -> Result<String, Error>;
434
435 fn get_file_mtime(&self, path: &Path) -> Result<u64, Error>;
437
438 fn is_executable(&self, path: &Path) -> Result<bool, Error>;
440
441 fn stored_kind(&self, path: &Path) -> Result<Kind, Error>;
443
444 fn supports_content_filtering(&self) -> bool;
446
447 fn supports_file_ids(&self) -> bool;
449
450 fn supports_rename_tracking(&self) -> bool;
452
453 fn supports_symlinks(&self) -> bool;
455
456 fn supports_tree_reference(&self) -> bool;
458
459 fn unknowns(&self) -> Result<Vec<PathBuf>, Error>;
461
462 fn all_versioned_paths(
464 &self,
465 ) -> Result<Box<dyn Iterator<Item = Result<PathBuf, Error>>>, Error>;
466
467 fn conflicts(&self) -> Result<Vec<Conflict>, Error>;
469
470 fn extras(&self) -> Result<Vec<PathBuf>, Error>;
472
473 fn filter_unversioned_files(&self, paths: &[&Path]) -> Result<Vec<PathBuf>, Error>;
475
476 fn walkdirs(
478 &self,
479 prefix: Option<&Path>,
480 ) -> Result<Box<dyn Iterator<Item = Result<WalkdirResult, Error>>>, Error>;
481
482 fn versionable_kind(&self, kind: &Kind) -> bool;
484
485 fn path_content_summary(&self, path: &Path) -> Result<PathContentSummary, Error>;
487
488 fn iter_files_bytes(
490 &self,
491 paths: &[&Path],
492 ) -> Result<Box<dyn Iterator<Item = Result<(PathBuf, Vec<u8>), Error>>>, Error>;
493
494 fn iter_entries_by_dir(
496 &self,
497 specific_files: Option<&[&Path]>,
498 ) -> Result<Box<dyn Iterator<Item = Result<(PathBuf, TreeEntry), Error>>>, Error>;
499
500 fn get_file_verifier(
502 &self,
503 path: &Path,
504 stat_value: Option<&std::fs::Metadata>,
505 ) -> Result<(String, Vec<u8>), Error>;
506
507 fn get_reference_revision(&self, path: &Path) -> Result<RevisionId, Error>;
509
510 fn archive(
512 &self,
513 format: &str,
514 name: &str,
515 root: Option<&str>,
516 subdir: Option<&Path>,
517 force_mtime: Option<f64>,
518 recurse_nested: bool,
519 ) -> Result<Box<dyn Iterator<Item = Result<Vec<u8>, Error>>>, Error>;
520
521 fn annotate_iter(
523 &self,
524 path: &Path,
525 default_revision: Option<&RevisionId>,
526 ) -> Result<Box<dyn Iterator<Item = Result<(RevisionId, Vec<u8>), Error>>>, Error>;
527
528 fn is_special_path(&self, path: &Path) -> bool;
530
531 fn iter_search_rules(
533 &self,
534 paths: &[&Path],
535 ) -> Result<Box<dyn Iterator<Item = Result<SearchRule, Error>>>, Error>;
536}
537
538pub trait PyTree: Tree + std::any::Any {
542 fn to_object(&self, py: Python) -> Py<PyAny>;
544}
545
546impl dyn PyTree {
547 pub fn as_tree(&self) -> &dyn Tree {
549 self
550 }
551}
552
553impl<T: PyTree + ?Sized> Tree for T {
554 fn get_tag_dict(&self) -> Result<std::collections::HashMap<String, RevisionId>, Error> {
555 Python::attach(|py| {
556 let branch = self.to_object(py).getattr(py, "branch")?;
557 let tags = branch.getattr(py, "tags")?;
558 let tag_dict = tags.call_method0(py, intern!(py, "get_tag_dict"))?;
559 tag_dict.extract(py)
560 })
561 .map_err(|e: PyErr| -> Error { e.into() })
562 }
563
564 fn get_file(&self, path: &Path) -> Result<Box<dyn std::io::Read>, Error> {
565 Python::attach(|py| {
566 let path_str = path.to_string_lossy().to_string();
567 let f = self
568 .to_object(py)
569 .call_method1(py, "get_file", (path_str,))?;
570
571 let f = pyo3_filelike::PyBinaryFile::from(f);
572
573 Ok(Box::new(f) as Box<dyn std::io::Read>)
574 })
575 }
576
577 fn get_file_text(&self, path: &Path) -> Result<Vec<u8>, Error> {
578 Python::attach(|py| {
579 let path_str = path.to_string_lossy().to_string();
580 let text = self
581 .to_object(py)
582 .call_method1(py, "get_file_text", (path_str,))?;
583 text.extract(py).map_err(Into::into)
584 })
585 }
586
587 fn get_file_lines(&self, path: &Path) -> Result<Vec<Vec<u8>>, Error> {
588 Python::attach(|py| {
589 let path_str = path.to_string_lossy().to_string();
590 let lines = self
591 .to_object(py)
592 .call_method1(py, "get_file_lines", (path_str,))?;
593 lines.extract(py).map_err(Into::into)
594 })
595 }
596
597 fn lock_read(&self) -> Result<Lock, Error> {
598 Python::attach(|py| {
599 let lock = self
600 .to_object(py)
601 .call_method0(py, intern!(py, "lock_read"))?;
602 Ok(Lock::from(lock))
603 })
604 }
605
606 fn has_filename(&self, path: &Path) -> bool {
607 Python::attach(|py| {
608 let path_str = path.to_string_lossy().to_string();
609 self.to_object(py)
610 .call_method1(py, intern!(py, "has_filename"), (path_str,))
611 .and_then(|result| result.extract(py))
612 .unwrap_or(false)
613 })
614 }
615
616 fn get_symlink_target(&self, path: &Path) -> Result<PathBuf, Error> {
617 Python::attach(|py| {
618 let path_str = path.to_string_lossy().to_string();
619 let target = self
620 .to_object(py)
621 .call_method1(py, "get_symlink_target", (path_str,))?;
622 target.extract(py).map_err(Into::into)
623 })
624 }
625
626 fn get_parent_ids(&self) -> Result<Vec<RevisionId>, Error> {
627 Python::attach(|py| {
628 Ok(self
629 .to_object(py)
630 .call_method0(py, intern!(py, "get_parent_ids"))
631 .unwrap()
632 .extract(py)?)
633 })
634 }
635
636 fn is_ignored(&self, path: &Path) -> Option<String> {
637 Python::attach(|py| {
638 let path_str = path.to_string_lossy().to_string();
639 self.to_object(py)
640 .call_method1(py, "is_ignored", (path_str,))
641 .unwrap()
642 .extract(py)
643 .unwrap()
644 })
645 }
646
647 fn kind(&self, path: &Path) -> Result<Kind, Error> {
648 Python::attach(|py| {
649 let path_str = path.to_string_lossy().to_string();
650 self.to_object(py)
651 .call_method1(py, "kind", (path_str,))
652 .unwrap()
653 .extract(py)
654 .map_err(Into::into)
655 })
656 }
657
658 fn is_versioned(&self, path: &Path) -> bool {
659 Python::attach(|py| {
660 let path_str = path.to_string_lossy().to_string();
661 self.to_object(py)
662 .call_method1(py, "is_versioned", (path_str,))
663 .unwrap()
664 .extract(py)
665 .unwrap()
666 })
667 }
668
669 fn iter_changes(
670 &self,
671 other: &dyn PyTree,
672 specific_files: Option<&[&Path]>,
673 want_unversioned: Option<bool>,
674 require_versioned: Option<bool>,
675 ) -> Result<Box<dyn Iterator<Item = Result<TreeChange, Error>>>, Error> {
676 Python::attach(|py| {
677 let kwargs = pyo3::types::PyDict::new(py);
678 if let Some(specific_files) = specific_files {
679 kwargs.set_item(
680 "specific_files",
681 specific_files
682 .iter()
683 .map(|p| p.to_string_lossy().to_string())
684 .collect::<Vec<_>>(),
685 )?;
686 }
687 if let Some(want_unversioned) = want_unversioned {
688 kwargs.set_item("want_unversioned", want_unversioned)?;
689 }
690 if let Some(require_versioned) = require_versioned {
691 kwargs.set_item("require_versioned", require_versioned)?;
692 }
693 struct TreeChangeIter(pyo3::Py<PyAny>);
694
695 impl Iterator for TreeChangeIter {
696 type Item = Result<TreeChange, Error>;
697
698 fn next(&mut self) -> Option<Self::Item> {
699 Python::attach(|py| {
700 let next = match self.0.call_method0(py, intern!(py, "__next__")) {
701 Ok(v) => v,
702 Err(e) => {
703 if e.is_instance_of::<pyo3::exceptions::PyStopIteration>(py) {
704 return None;
705 }
706 return Some(Err(e.into()));
707 }
708 };
709
710 if next.is_none(py) {
711 None
712 } else {
713 Some(next.extract(py).map_err(Into::into))
714 }
715 })
716 }
717 }
718
719 Ok(Box::new(TreeChangeIter(self.to_object(py).call_method(
720 py,
721 "iter_changes",
722 (other.to_object(py),),
723 Some(&kwargs),
724 )?))
725 as Box<dyn Iterator<Item = Result<TreeChange, Error>>>)
726 })
727 }
728
729 fn has_versioned_directories(&self) -> bool {
730 Python::attach(|py| {
731 self.to_object(py)
732 .call_method0(py, "has_versioned_directories")
733 .unwrap()
734 .extract(py)
735 .unwrap()
736 })
737 }
738
739 fn preview_transform(&self) -> Result<crate::transform::TreeTransform, Error> {
740 Python::attach(|py| {
741 let transform = self.to_object(py).call_method0(py, "preview_transform")?;
742 Ok(crate::transform::TreeTransform::from(transform))
743 })
744 }
745
746 fn list_files(
747 &self,
748 include_root: Option<bool>,
749 from_dir: Option<&Path>,
750 recursive: Option<bool>,
751 recurse_nested: Option<bool>,
752 ) -> Result<Box<dyn Iterator<Item = Result<(PathBuf, bool, Kind, TreeEntry), Error>>>, Error>
753 {
754 Python::attach(|py| {
755 let kwargs = pyo3::types::PyDict::new(py);
756 if let Some(include_root) = include_root {
757 kwargs.set_item("include_root", include_root)?;
758 }
759 if let Some(from_dir) = from_dir {
760 kwargs.set_item("from_dir", from_dir.to_string_lossy().to_string())?;
761 }
762 if let Some(recursive) = recursive {
763 kwargs.set_item("recursive", recursive)?;
764 }
765 if let Some(recurse_nested) = recurse_nested {
766 kwargs.set_item("recurse_nested", recurse_nested)?;
767 }
768 struct ListFilesIter(pyo3::Py<PyAny>);
769
770 impl Iterator for ListFilesIter {
771 type Item = Result<(PathBuf, bool, Kind, TreeEntry), Error>;
772
773 fn next(&mut self) -> Option<Self::Item> {
774 Python::attach(|py| {
775 let next = match self.0.call_method0(py, intern!(py, "__next__")) {
776 Ok(v) => v,
777 Err(e) => {
778 if e.is_instance_of::<pyo3::exceptions::PyStopIteration>(py) {
779 return None;
780 }
781 return Some(Err(e.into()));
782 }
783 };
784
785 if next.is_none(py) {
786 None
787 } else {
788 Some(next.extract(py).map_err(Into::into))
789 }
790 })
791 }
792 }
793
794 Ok(Box::new(ListFilesIter(self.to_object(py).call_method(
795 py,
796 "list_files",
797 (),
798 Some(&kwargs),
799 )?))
800 as Box<
801 dyn Iterator<Item = Result<(PathBuf, bool, Kind, TreeEntry), Error>>,
802 >)
803 })
804 .map_err(|e: PyErr| -> Error { e.into() })
805 }
806
807 fn iter_child_entries(
808 &self,
809 path: &std::path::Path,
810 ) -> Result<Box<dyn Iterator<Item = Result<(PathBuf, Kind, TreeEntry), Error>>>, Error> {
811 Python::attach(|py| {
812 struct IterChildEntriesIter(pyo3::Py<PyAny>);
813
814 impl Iterator for IterChildEntriesIter {
815 type Item = Result<(PathBuf, Kind, TreeEntry), Error>;
816
817 fn next(&mut self) -> Option<Self::Item> {
818 Python::attach(|py| {
819 let next = match self.0.call_method0(py, intern!(py, "__next__")) {
820 Ok(v) => v,
821 Err(e) => {
822 if e.is_instance_of::<pyo3::exceptions::PyStopIteration>(py) {
823 return None;
824 }
825 return Some(Err(e.into()));
826 }
827 };
828
829 if next.is_none(py) {
830 None
831 } else {
832 Some(next.extract(py).map_err(Into::into))
833 }
834 })
835 }
836 }
837
838 let path_str = path.to_string_lossy().to_string();
839 Ok(
840 Box::new(IterChildEntriesIter(self.to_object(py).call_method1(
841 py,
842 "iter_child_entries",
843 (path_str,),
844 )?))
845 as Box<dyn Iterator<Item = Result<(PathBuf, Kind, TreeEntry), Error>>>,
846 )
847 })
848 }
849
850 fn get_file_size(&self, path: &Path) -> Result<u64, Error> {
851 Python::attach(|py| {
852 let path_str = path.to_string_lossy().to_string();
853 let size = self
854 .to_object(py)
855 .call_method1(py, "get_file_size", (path_str,))?;
856 size.extract(py).map_err(Into::into)
857 })
858 }
859
860 fn get_file_sha1(
861 &self,
862 path: &Path,
863 _stat_value: Option<&std::fs::Metadata>,
864 ) -> Result<String, Error> {
865 Python::attach(|py| {
866 let path_str = path.to_string_lossy().to_string();
867 let sha1 = self
868 .to_object(py)
869 .call_method1(py, "get_file_sha1", (path_str,))?;
870 sha1.extract(py).map_err(Into::into)
871 })
872 }
873
874 fn get_file_mtime(&self, path: &Path) -> Result<u64, Error> {
875 Python::attach(|py| {
876 let path_str = path.to_string_lossy().to_string();
877 let mtime = self
878 .to_object(py)
879 .call_method1(py, "get_file_mtime", (path_str,))?;
880 mtime.extract(py).map_err(Into::into)
881 })
882 }
883
884 fn is_executable(&self, path: &Path) -> Result<bool, Error> {
885 Python::attach(|py| {
886 let path_str = path.to_string_lossy().to_string();
887 let result = self
888 .to_object(py)
889 .call_method1(py, "is_executable", (path_str,))?;
890 result.extract(py).map_err(Into::into)
891 })
892 }
893
894 fn stored_kind(&self, path: &Path) -> Result<Kind, Error> {
895 Python::attach(|py| {
896 let path_str = path.to_string_lossy().to_string();
897 self.to_object(py)
898 .call_method1(py, "stored_kind", (path_str,))?
899 .extract(py)
900 .map_err(Into::into)
901 })
902 }
903
904 fn supports_content_filtering(&self) -> bool {
905 Python::attach(|py| {
906 self.to_object(py)
907 .call_method0(py, "supports_content_filtering")
908 .unwrap()
909 .extract(py)
910 .unwrap()
911 })
912 }
913
914 fn supports_file_ids(&self) -> bool {
915 Python::attach(|py| {
916 self.to_object(py)
917 .call_method0(py, "supports_file_ids")
918 .unwrap()
919 .extract(py)
920 .unwrap()
921 })
922 }
923
924 fn supports_rename_tracking(&self) -> bool {
925 Python::attach(|py| {
926 self.to_object(py)
927 .call_method0(py, "supports_rename_tracking")
928 .unwrap()
929 .extract(py)
930 .unwrap()
931 })
932 }
933
934 fn supports_symlinks(&self) -> bool {
935 Python::attach(|py| {
936 self.to_object(py)
937 .call_method0(py, "supports_symlinks")
938 .unwrap()
939 .extract(py)
940 .unwrap()
941 })
942 }
943
944 fn supports_tree_reference(&self) -> bool {
945 Python::attach(|py| {
946 self.to_object(py)
947 .call_method0(py, "supports_tree_reference")
948 .unwrap()
949 .extract(py)
950 .unwrap()
951 })
952 }
953
954 fn unknowns(&self) -> Result<Vec<PathBuf>, Error> {
955 Python::attach(|py| {
956 let unknowns = self.to_object(py).call_method0(py, "unknowns")?;
957 unknowns.extract(py).map_err(Into::into)
958 })
959 }
960
961 fn all_versioned_paths(
962 &self,
963 ) -> Result<Box<dyn Iterator<Item = Result<PathBuf, Error>>>, Error> {
964 Python::attach(|py| {
965 struct AllVersionedPathsIter(pyo3::Py<PyAny>);
966
967 impl Iterator for AllVersionedPathsIter {
968 type Item = Result<PathBuf, Error>;
969
970 fn next(&mut self) -> Option<Self::Item> {
971 Python::attach(|py| {
972 let next = match self.0.call_method0(py, intern!(py, "__next__")) {
973 Ok(v) => v,
974 Err(e) => {
975 if e.is_instance_of::<pyo3::exceptions::PyStopIteration>(py) {
976 return None;
977 }
978 return Some(Err(e.into()));
979 }
980 };
981
982 if next.is_none(py) {
983 None
984 } else {
985 Some(next.extract(py).map_err(Into::into))
986 }
987 })
988 }
989 }
990
991 Ok(Box::new(AllVersionedPathsIter(
992 self.to_object(py).call_method0(py, "all_versioned_paths")?,
993 ))
994 as Box<dyn Iterator<Item = Result<PathBuf, Error>>>)
995 })
996 }
997
998 fn conflicts(&self) -> Result<Vec<Conflict>, Error> {
999 Python::attach(|py| {
1000 let conflicts = self.to_object(py).call_method0(py, "conflicts")?;
1001 conflicts.extract(py).map_err(Into::into)
1002 })
1003 }
1004
1005 fn extras(&self) -> Result<Vec<PathBuf>, Error> {
1006 Python::attach(|py| {
1007 let extras = self.to_object(py).call_method0(py, "extras")?;
1008 extras.extract(py).map_err(Into::into)
1009 })
1010 }
1011
1012 fn filter_unversioned_files(&self, paths: &[&Path]) -> Result<Vec<PathBuf>, Error> {
1013 Python::attach(|py| {
1014 let path_strings: Vec<String> = paths
1015 .iter()
1016 .map(|p| p.to_string_lossy().to_string())
1017 .collect();
1018 let result =
1019 self.to_object(py)
1020 .call_method1(py, "filter_unversioned_files", (path_strings,))?;
1021 result.extract(py).map_err(Into::into)
1022 })
1023 }
1024
1025 fn walkdirs(
1026 &self,
1027 prefix: Option<&Path>,
1028 ) -> Result<Box<dyn Iterator<Item = Result<WalkdirResult, Error>>>, Error> {
1029 Python::attach(|py| {
1030 type EntryTuple = (String, String, String, Option<Py<PyAny>>, Option<String>);
1033
1034 fn process_entry(entry: &EntryTuple) -> Result<WalkdirResult, Error> {
1036 let kind = entry.2.parse().map_err(|_| {
1037 Error::Other(Python::attach(|_py| {
1038 PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
1039 "Invalid kind: {}",
1040 entry.2
1041 ))
1042 }))
1043 })?;
1044
1045 let versioned = entry.4.is_some();
1047
1048 let stat = if let Some(ref py_stat) = entry.3 {
1050 convert_python_stat_to_metadata(py_stat)?
1051 } else {
1052 None
1053 };
1054
1055 Ok(WalkdirResult {
1056 relpath: PathBuf::from(&entry.0),
1057 kind,
1058 stat,
1059 versioned,
1060 })
1061 }
1062
1063 struct WalkdirsIter {
1064 py_iter: pyo3::Py<PyAny>,
1065 current_entries: Vec<EntryTuple>,
1066 current_index: usize,
1067 }
1068
1069 impl Iterator for WalkdirsIter {
1070 type Item = Result<WalkdirResult, Error>;
1071
1072 fn next(&mut self) -> Option<Self::Item> {
1073 Python::attach(|py| {
1074 if self.current_index < self.current_entries.len() {
1076 let entry = &self.current_entries[self.current_index];
1077 self.current_index += 1;
1078 return Some(process_entry(entry));
1079 }
1080
1081 loop {
1083 let next = match self.py_iter.call_method0(py, intern!(py, "__next__"))
1084 {
1085 Ok(v) => v,
1086 Err(e) => {
1087 if e.is_instance_of::<pyo3::exceptions::PyStopIteration>(py) {
1088 return None;
1089 }
1090 return Some(Err(e.into()));
1091 }
1092 };
1093
1094 if next.is_none(py) {
1095 return None;
1096 }
1097
1098 let tuple: (String, Vec<EntryTuple>) = match next.extract(py) {
1100 Ok(t) => t,
1101 Err(e) => return Some(Err(e.into())),
1102 };
1103
1104 self.current_entries = tuple.1;
1105 self.current_index = 0;
1106
1107 if !self.current_entries.is_empty() {
1109 let entry = &self.current_entries[0];
1110 self.current_index = 1;
1111 return Some(process_entry(entry));
1112 }
1113 }
1115 })
1116 }
1117 }
1118
1119 let prefix_str = match prefix {
1121 Some(p) => p.to_string_lossy().to_string(),
1122 None => "".to_string(),
1123 };
1124 let py_iter = self
1125 .to_object(py)
1126 .call_method1(py, "walkdirs", (prefix_str,))?;
1127
1128 Ok(Box::new(WalkdirsIter {
1129 py_iter,
1130 current_entries: Vec::new(),
1131 current_index: 0,
1132 })
1133 as Box<dyn Iterator<Item = Result<WalkdirResult, Error>>>)
1134 })
1135 }
1136
1137 fn versionable_kind(&self, kind: &Kind) -> bool {
1138 Python::attach(|py| {
1139 self.to_object(py)
1140 .call_method1(py, "versionable_kind", (kind.clone(),))
1141 .unwrap()
1142 .extract(py)
1143 .unwrap()
1144 })
1145 }
1146
1147 fn path_content_summary(&self, path: &Path) -> Result<PathContentSummary, Error> {
1148 Python::attach(|py| {
1149 let path_str = path.to_string_lossy().to_string();
1150 let summary =
1151 self.to_object(py)
1152 .call_method1(py, "path_content_summary", (path_str,))?;
1153
1154 let summary_bound = summary.bind(py);
1155 let kind: String = summary_bound.get_item("kind")?.extract()?;
1156 let size: Option<u64> = summary_bound
1157 .get_item("size")
1158 .ok()
1159 .map(|v| v.extract().expect("size should be u64"));
1160 let executable: Option<bool> = summary_bound
1161 .get_item("executable")
1162 .ok()
1163 .map(|v| v.extract().expect("executable should be bool"));
1164 let sha1: Option<String> = summary_bound
1165 .get_item("sha1")
1166 .ok()
1167 .map(|v| v.extract().expect("sha1 should be string"));
1168 let target: Option<String> = summary_bound
1169 .get_item("target")
1170 .ok()
1171 .map(|v| v.extract().expect("target should be string"));
1172
1173 Ok(PathContentSummary {
1174 kind: kind.parse().unwrap(),
1175 size,
1176 executable,
1177 sha1,
1178 target,
1179 })
1180 })
1181 }
1182
1183 fn iter_files_bytes(
1184 &self,
1185 paths: &[&Path],
1186 ) -> Result<Box<dyn Iterator<Item = Result<(PathBuf, Vec<u8>), Error>>>, Error> {
1187 Python::attach(|py| {
1188 struct IterFilesBytesIter(pyo3::Py<PyAny>);
1189
1190 impl Iterator for IterFilesBytesIter {
1191 type Item = Result<(PathBuf, Vec<u8>), Error>;
1192
1193 fn next(&mut self) -> Option<Self::Item> {
1194 Python::attach(|py| {
1195 let next = match self.0.call_method0(py, intern!(py, "__next__")) {
1196 Ok(v) => v,
1197 Err(e) => {
1198 if e.is_instance_of::<pyo3::exceptions::PyStopIteration>(py) {
1199 return None;
1200 }
1201 return Some(Err(e.into()));
1202 }
1203 };
1204
1205 if next.is_none(py) {
1206 None
1207 } else {
1208 Some(next.extract(py).map_err(Into::into))
1209 }
1210 })
1211 }
1212 }
1213
1214 let path_strings: Vec<String> = paths
1215 .iter()
1216 .map(|p| p.to_string_lossy().to_string())
1217 .collect();
1218 Ok(Box::new(IterFilesBytesIter(self.to_object(py).call_method1(
1219 py,
1220 "iter_files_bytes",
1221 (path_strings,),
1222 )?))
1223 as Box<
1224 dyn Iterator<Item = Result<(PathBuf, Vec<u8>), Error>>,
1225 >)
1226 })
1227 }
1228
1229 fn iter_entries_by_dir(
1230 &self,
1231 specific_files: Option<&[&Path]>,
1232 ) -> Result<Box<dyn Iterator<Item = Result<(PathBuf, TreeEntry), Error>>>, Error> {
1233 Python::attach(|py| {
1234 struct IterEntriesByDirIter(pyo3::Py<PyAny>);
1235
1236 impl Iterator for IterEntriesByDirIter {
1237 type Item = Result<(PathBuf, TreeEntry), Error>;
1238
1239 fn next(&mut self) -> Option<Self::Item> {
1240 Python::attach(|py| {
1241 let next = match self.0.call_method0(py, intern!(py, "__next__")) {
1242 Ok(v) => v,
1243 Err(e) => {
1244 if e.is_instance_of::<pyo3::exceptions::PyStopIteration>(py) {
1245 return None;
1246 }
1247 return Some(Err(e.into()));
1248 }
1249 };
1250
1251 if next.is_none(py) {
1252 None
1253 } else {
1254 Some(next.extract(py).map_err(Into::into))
1255 }
1256 })
1257 }
1258 }
1259
1260 let kwargs = pyo3::types::PyDict::new(py);
1261 if let Some(specific_files) = specific_files {
1262 let path_strings: Vec<String> = specific_files
1263 .iter()
1264 .map(|p| p.to_string_lossy().to_string())
1265 .collect();
1266 kwargs.set_item("specific_files", path_strings)?;
1267 }
1268
1269 Ok(
1270 Box::new(IterEntriesByDirIter(self.to_object(py).call_method(
1271 py,
1272 "iter_entries_by_dir",
1273 (),
1274 Some(&kwargs),
1275 )?))
1276 as Box<dyn Iterator<Item = Result<(PathBuf, TreeEntry), Error>>>,
1277 )
1278 })
1279 }
1280
1281 fn get_file_verifier(
1282 &self,
1283 path: &Path,
1284 _stat_value: Option<&std::fs::Metadata>,
1285 ) -> Result<(String, Vec<u8>), Error> {
1286 Python::attach(|py| {
1287 let path_str = path.to_string_lossy().to_string();
1288 let result = self
1289 .to_object(py)
1290 .call_method1(py, "get_file_verifier", (path_str,))?;
1291 result.extract(py).map_err(Into::into)
1292 })
1293 }
1294
1295 fn get_reference_revision(&self, path: &Path) -> Result<RevisionId, Error> {
1296 Python::attach(|py| {
1297 let path_str = path.to_string_lossy().to_string();
1298 let rev = self
1299 .to_object(py)
1300 .call_method1(py, "get_reference_revision", (path_str,))?;
1301 rev.extract(py).map_err(Into::into)
1302 })
1303 }
1304
1305 fn archive(
1306 &self,
1307 format: &str,
1308 name: &str,
1309 root: Option<&str>,
1310 subdir: Option<&Path>,
1311 force_mtime: Option<f64>,
1312 recurse_nested: bool,
1313 ) -> Result<Box<dyn Iterator<Item = Result<Vec<u8>, Error>>>, Error> {
1314 Python::attach(|py| {
1315 struct ArchiveIter(pyo3::Py<PyAny>);
1316
1317 impl Iterator for ArchiveIter {
1318 type Item = Result<Vec<u8>, Error>;
1319
1320 fn next(&mut self) -> Option<Self::Item> {
1321 Python::attach(|py| {
1322 let next = match self.0.call_method0(py, intern!(py, "__next__")) {
1323 Ok(v) => v,
1324 Err(e) => {
1325 if e.is_instance_of::<pyo3::exceptions::PyStopIteration>(py) {
1326 return None;
1327 }
1328 return Some(Err(e.into()));
1329 }
1330 };
1331
1332 if next.is_none(py) {
1333 None
1334 } else {
1335 Some(next.extract(py).map_err(Into::into))
1336 }
1337 })
1338 }
1339 }
1340
1341 let kwargs = pyo3::types::PyDict::new(py);
1342 kwargs.set_item("format", format)?;
1343 kwargs.set_item("name", name)?;
1344 if let Some(root) = root {
1345 kwargs.set_item("root", root)?;
1346 }
1347 if let Some(subdir) = subdir {
1348 kwargs.set_item("subdir", subdir.to_string_lossy().to_string())?;
1349 }
1350 if let Some(force_mtime) = force_mtime {
1351 kwargs.set_item("force_mtime", force_mtime)?;
1352 }
1353 kwargs.set_item("recurse_nested", recurse_nested)?;
1354
1355 Ok(Box::new(ArchiveIter(self.to_object(py).call_method(
1356 py,
1357 "archive",
1358 (),
1359 Some(&kwargs),
1360 )?))
1361 as Box<dyn Iterator<Item = Result<Vec<u8>, Error>>>)
1362 })
1363 }
1364
1365 fn annotate_iter(
1366 &self,
1367 path: &Path,
1368 default_revision: Option<&RevisionId>,
1369 ) -> Result<Box<dyn Iterator<Item = Result<(RevisionId, Vec<u8>), Error>>>, Error> {
1370 Python::attach(|py| {
1371 struct AnnotateIter(pyo3::Py<PyAny>);
1372
1373 impl Iterator for AnnotateIter {
1374 type Item = Result<(RevisionId, Vec<u8>), Error>;
1375
1376 fn next(&mut self) -> Option<Self::Item> {
1377 Python::attach(|py| {
1378 let next = match self.0.call_method0(py, intern!(py, "__next__")) {
1379 Ok(v) => v,
1380 Err(e) => {
1381 if e.is_instance_of::<pyo3::exceptions::PyStopIteration>(py) {
1382 return None;
1383 }
1384 return Some(Err(e.into()));
1385 }
1386 };
1387
1388 if next.is_none(py) {
1389 None
1390 } else {
1391 Some(next.extract(py).map_err(Into::into))
1392 }
1393 })
1394 }
1395 }
1396
1397 let path_str = path.to_string_lossy().to_string();
1398 let kwargs = pyo3::types::PyDict::new(py);
1399 if let Some(default_revision) = default_revision {
1400 kwargs.set_item(
1401 "default_revision",
1402 default_revision.clone().into_pyobject(py).unwrap(),
1403 )?;
1404 }
1405
1406 Ok(Box::new(AnnotateIter(self.to_object(py).call_method(
1407 py,
1408 "annotate_iter",
1409 (path_str,),
1410 Some(&kwargs),
1411 )?))
1412 as Box<
1413 dyn Iterator<Item = Result<(RevisionId, Vec<u8>), Error>>,
1414 >)
1415 })
1416 }
1417
1418 fn is_special_path(&self, path: &Path) -> bool {
1419 Python::attach(|py| {
1420 let path_str = path.to_string_lossy().to_string();
1421 self.to_object(py)
1422 .call_method1(py, "is_special_path", (path_str,))
1423 .unwrap()
1424 .extract(py)
1425 .unwrap()
1426 })
1427 }
1428
1429 fn iter_search_rules(
1430 &self,
1431 paths: &[&Path],
1432 ) -> Result<Box<dyn Iterator<Item = Result<SearchRule, Error>>>, Error> {
1433 Python::attach(|py| {
1434 struct IterSearchRulesIter(pyo3::Py<PyAny>);
1435
1436 impl Iterator for IterSearchRulesIter {
1437 type Item = Result<SearchRule, Error>;
1438
1439 fn next(&mut self) -> Option<Self::Item> {
1440 Python::attach(|py| {
1441 let next = match self.0.call_method0(py, intern!(py, "__next__")) {
1442 Ok(v) => v,
1443 Err(e) => {
1444 if e.is_instance_of::<pyo3::exceptions::PyStopIteration>(py) {
1445 return None;
1446 }
1447 return Some(Err(e.into()));
1448 }
1449 };
1450
1451 if next.is_none(py) {
1452 None
1453 } else {
1454 let tuple = match next.extract::<(String, String)>(py) {
1455 Ok(t) => t,
1456 Err(e) => return Some(Err(e.into())),
1457 };
1458
1459 let rule_type = match tuple.1.as_str() {
1460 "include" => SearchRuleType::Include,
1461 "exclude" => SearchRuleType::Exclude,
1462 _ => {
1463 return Some(Err(Error::Other(PyErr::new::<
1464 pyo3::exceptions::PyValueError,
1465 _,
1466 >(
1467 "Unknown search rule type"
1468 ))))
1469 }
1470 };
1471
1472 Some(Ok(SearchRule {
1473 pattern: tuple.0,
1474 rule_type,
1475 }))
1476 }
1477 })
1478 }
1479 }
1480
1481 let path_strings: Vec<String> = paths
1482 .iter()
1483 .map(|p| p.to_string_lossy().to_string())
1484 .collect();
1485 Ok(
1486 Box::new(IterSearchRulesIter(self.to_object(py).call_method1(
1487 py,
1488 "iter_search_rules",
1489 (path_strings,),
1490 )?)) as Box<dyn Iterator<Item = Result<SearchRule, Error>>>,
1491 )
1492 })
1493 }
1494}
1495
1496pub struct GenericTree(Py<PyAny>);
1498
1499impl<'py> IntoPyObject<'py> for GenericTree {
1500 type Target = PyAny;
1501 type Output = Bound<'py, Self::Target>;
1502 type Error = std::convert::Infallible;
1503
1504 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
1505 Ok(self.0.into_bound(py))
1506 }
1507}
1508
1509impl From<Py<PyAny>> for GenericTree {
1510 fn from(obj: Py<PyAny>) -> Self {
1511 GenericTree(obj)
1512 }
1513}
1514
1515impl PyTree for GenericTree {
1516 fn to_object(&self, py: Python) -> Py<PyAny> {
1517 self.0.clone_ref(py)
1518 }
1519}
1520
1521pub trait MutableTree: Tree {
1523 fn add(&self, files: &[&Path]) -> Result<(), Error>;
1525 fn lock_write(&self) -> Result<Lock, Error>;
1527 fn put_file_bytes_non_atomic(&self, path: &Path, data: &[u8]) -> Result<(), Error>;
1529 fn has_changes(&self) -> std::result::Result<bool, Error>;
1531 fn mkdir(&self, path: &Path) -> Result<(), Error>;
1533 fn remove(&self, files: &[&std::path::Path]) -> Result<(), Error>;
1535
1536 fn add_reference(&self, reference: &TreeReference) -> Result<(), Error>;
1538
1539 fn copy_one(&self, from_path: &Path, to_path: &Path) -> Result<(), Error>;
1541
1542 fn last_revision(&self) -> Result<RevisionId, Error>;
1544
1545 fn lock_tree_write(&self) -> Result<Lock, Error>;
1547
1548 fn set_parent_ids(&self, parent_ids: &[RevisionId]) -> Result<(), Error>;
1550
1551 fn set_parent_trees(&self, parent_trees: &[(RevisionId, RevisionTree)]) -> Result<(), Error>;
1553
1554 fn apply_inventory_delta(&self, delta: Vec<InventoryDelta>) -> Result<(), Error>;
1556
1557 fn commit(
1559 &self,
1560 message: &str,
1561 committer: Option<&str>,
1562 timestamp: Option<f64>,
1563 allow_pointless: Option<bool>,
1564 specific_files: Option<&[&Path]>,
1565 ) -> Result<RevisionId, Error>;
1566}
1567
1568pub trait PyMutableTree: PyTree + MutableTree {}
1570
1571impl dyn PyMutableTree {
1572 pub fn as_mutable_tree(&self) -> &dyn MutableTree {
1574 self
1575 }
1576}
1577
1578impl<T: PyMutableTree + ?Sized> MutableTree for T {
1579 fn add(&self, files: &[&Path]) -> Result<(), Error> {
1580 for f in files {
1581 assert!(f.is_relative());
1582 }
1583 Python::attach(|py| -> Result<(), PyErr> {
1584 let path_strings: Vec<String> = files
1585 .iter()
1586 .map(|p| p.to_string_lossy().to_string())
1587 .collect();
1588 self.to_object(py)
1589 .call_method1(py, "add", (path_strings,))?;
1590 Ok(())
1591 })
1592 .map_err(Into::into)
1593 }
1594
1595 fn lock_write(&self) -> Result<Lock, Error> {
1596 Python::attach(|py| {
1597 let lock = self
1598 .to_object(py)
1599 .call_method0(py, intern!(py, "lock_write"))?;
1600 Ok(Lock::from(lock))
1601 })
1602 }
1603
1604 fn put_file_bytes_non_atomic(&self, path: &Path, data: &[u8]) -> Result<(), Error> {
1605 assert!(path.is_relative());
1606 Python::attach(|py| {
1607 let path_str = path.to_string_lossy().to_string();
1608 self.to_object(py)
1609 .call_method1(py, "put_file_bytes_non_atomic", (path_str, data))?;
1610 Ok(())
1611 })
1612 }
1613
1614 fn has_changes(&self) -> std::result::Result<bool, Error> {
1615 Python::attach(|py| {
1616 self.to_object(py)
1617 .call_method0(py, "has_changes")?
1618 .extract::<bool>(py)
1619 .map_err(Into::into)
1620 })
1621 }
1622
1623 fn mkdir(&self, path: &Path) -> Result<(), Error> {
1624 assert!(path.is_relative());
1625 Python::attach(|py| -> Result<(), PyErr> {
1626 let path_str = path.to_string_lossy().to_string();
1627 self.to_object(py).call_method1(py, "mkdir", (path_str,))?;
1628 Ok(())
1629 })
1630 .map_err(Into::into)
1631 }
1632
1633 fn remove(&self, files: &[&std::path::Path]) -> Result<(), Error> {
1634 for f in files {
1635 assert!(f.is_relative());
1636 }
1637 Python::attach(|py| -> Result<(), PyErr> {
1638 let path_strings: Vec<String> = files
1639 .iter()
1640 .map(|p| p.to_string_lossy().to_string())
1641 .collect();
1642 self.to_object(py)
1643 .call_method1(py, "remove", (path_strings,))?;
1644 Ok(())
1645 })
1646 .map_err(Into::into)
1647 }
1648
1649 fn add_reference(&self, reference: &TreeReference) -> Result<(), Error> {
1650 Python::attach(|py| {
1651 let kwargs = pyo3::types::PyDict::new(py);
1652 kwargs.set_item("path", reference.path.to_string_lossy().to_string())?;
1653 kwargs.set_item("kind", reference.kind.clone())?;
1654 if let Some(ref rev) = reference.reference_revision {
1655 kwargs.set_item("reference_revision", rev.clone().into_pyobject(py).unwrap())?;
1656 }
1657 self.to_object(py)
1658 .call_method(py, "add_reference", (), Some(&kwargs))?;
1659 Ok(())
1660 })
1661 }
1662
1663 fn copy_one(&self, from_path: &Path, to_path: &Path) -> Result<(), Error> {
1664 assert!(from_path.is_relative());
1665 assert!(to_path.is_relative());
1666 Python::attach(|py| {
1667 let from_str = from_path.to_string_lossy().to_string();
1668 let to_str = to_path.to_string_lossy().to_string();
1669 self.to_object(py)
1670 .call_method1(py, "copy_one", (from_str, to_str))?;
1671 Ok(())
1672 })
1673 }
1674
1675 fn last_revision(&self) -> Result<RevisionId, Error> {
1676 Python::attach(|py| {
1677 let last_revision = self
1678 .to_object(py)
1679 .call_method0(py, intern!(py, "last_revision"))?;
1680 Ok(RevisionId::from(last_revision.extract::<Vec<u8>>(py)?))
1681 })
1682 }
1683
1684 fn lock_tree_write(&self) -> Result<Lock, Error> {
1685 Python::attach(|py| {
1686 let lock = self.to_object(py).call_method0(py, "lock_tree_write")?;
1687 Ok(Lock::from(lock))
1688 })
1689 }
1690
1691 fn set_parent_ids(&self, parent_ids: &[RevisionId]) -> Result<(), Error> {
1692 Python::attach(|py| {
1693 let parent_ids_py: Vec<Py<PyAny>> = parent_ids
1694 .iter()
1695 .map(|id| id.clone().into_pyobject(py).unwrap().unbind())
1696 .collect();
1697 self.to_object(py)
1698 .call_method1(py, "set_parent_ids", (parent_ids_py,))?;
1699 Ok(())
1700 })
1701 }
1702
1703 fn set_parent_trees(&self, parent_trees: &[(RevisionId, RevisionTree)]) -> Result<(), Error> {
1704 Python::attach(|py| {
1705 let parent_trees_py: Vec<(Py<PyAny>, Py<PyAny>)> = parent_trees
1706 .iter()
1707 .map(|(id, tree)| {
1708 (
1709 id.clone().into_pyobject(py).unwrap().unbind(),
1710 tree.to_object(py),
1711 )
1712 })
1713 .collect();
1714 self.to_object(py)
1715 .call_method1(py, "set_parent_trees", (parent_trees_py,))?;
1716 Ok(())
1717 })
1718 }
1719
1720 fn apply_inventory_delta(&self, delta: Vec<InventoryDelta>) -> Result<(), Error> {
1721 Python::attach(|py| {
1722 let delta_py: Vec<Py<PyAny>> = delta
1723 .into_iter()
1724 .map(|d| {
1725 let tuple = pyo3::types::PyTuple::new(
1726 py,
1727 vec![
1728 d.old_path
1729 .map(|p| p.to_string_lossy().to_string())
1730 .into_pyobject(py)
1731 .unwrap()
1732 .into_any(),
1733 d.new_path
1734 .map(|p| p.to_string_lossy().to_string())
1735 .into_pyobject(py)
1736 .unwrap()
1737 .into_any(),
1738 d.file_id.into_pyobject(py).unwrap().into_any(),
1739 d.entry.into_pyobject(py).unwrap().into_any(),
1740 ],
1741 )
1742 .unwrap();
1743 tuple.into_any().unbind()
1744 })
1745 .collect();
1746 self.to_object(py)
1747 .call_method1(py, "apply_inventory_delta", (delta_py,))?;
1748 Ok(())
1749 })
1750 }
1751
1752 fn commit(
1753 &self,
1754 message: &str,
1755 committer: Option<&str>,
1756 timestamp: Option<f64>,
1757 allow_pointless: Option<bool>,
1758 specific_files: Option<&[&Path]>,
1759 ) -> Result<RevisionId, Error> {
1760 Python::attach(|py| {
1761 let kwargs = pyo3::types::PyDict::new(py);
1762 if let Some(committer) = committer {
1763 kwargs.set_item("committer", committer)?;
1764 }
1765 if let Some(timestamp) = timestamp {
1766 kwargs.set_item("timestamp", timestamp)?;
1767 }
1768 if let Some(allow_pointless) = allow_pointless {
1769 kwargs.set_item("allow_pointless", allow_pointless)?;
1770 }
1771 if let Some(specific_files) = specific_files {
1772 let file_paths: Vec<String> = specific_files
1773 .iter()
1774 .map(|p| p.to_string_lossy().to_string())
1775 .collect();
1776 kwargs.set_item("specific_files", file_paths)?;
1777 }
1778 let result = self
1779 .to_object(py)
1780 .call_method(py, "commit", (message,), Some(&kwargs))?;
1781 result.extract(py).map_err(Into::into)
1782 })
1783 }
1784}
1785
1786pub struct RevisionTree(pub Py<PyAny>);
1788
1789impl<'py> IntoPyObject<'py> for RevisionTree {
1790 type Target = PyAny;
1791 type Output = Bound<'py, Self::Target>;
1792 type Error = std::convert::Infallible;
1793
1794 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
1795 Ok(self.0.into_bound(py))
1796 }
1797}
1798
1799impl PyTree for RevisionTree {
1800 fn to_object(&self, py: Python) -> Py<PyAny> {
1801 self.0.clone_ref(py)
1802 }
1803}
1804
1805impl Clone for RevisionTree {
1806 fn clone(&self) -> Self {
1807 Python::attach(|py| RevisionTree(self.0.clone_ref(py)))
1808 }
1809}
1810
1811impl RevisionTree {
1812 pub fn repository(&self) -> crate::repository::GenericRepository {
1814 Python::attach(|py| {
1815 let repository = self.to_object(py).getattr(py, "_repository").unwrap();
1816 crate::repository::GenericRepository::new(repository)
1817 })
1818 }
1819
1820 pub fn get_revision_id(&self) -> RevisionId {
1822 Python::attach(|py| {
1823 self.to_object(py)
1824 .call_method0(py, "get_revision_id")
1825 .unwrap()
1826 .extract(py)
1827 .unwrap()
1828 })
1829 }
1830
1831 pub fn get_parent_ids(&self) -> Vec<RevisionId> {
1833 Python::attach(|py| {
1834 self.to_object(py)
1835 .call_method0(py, intern!(py, "get_parent_ids"))
1836 .unwrap()
1837 .extract(py)
1838 .unwrap()
1839 })
1840 }
1841}
1842
1843#[derive(Debug, PartialEq, Eq, Clone)]
1844pub struct TreeChange {
1846 pub path: (Option<PathBuf>, Option<PathBuf>),
1848 pub changed_content: bool,
1850 pub versioned: (Option<bool>, Option<bool>),
1852 pub name: (Option<std::ffi::OsString>, Option<std::ffi::OsString>),
1854 pub kind: (Option<Kind>, Option<Kind>),
1856 pub executable: (Option<bool>, Option<bool>),
1858 pub copied: bool,
1860}
1861
1862impl<'py> IntoPyObject<'py> for TreeChange {
1863 type Target = PyAny;
1864 type Output = Bound<'py, Self::Target>;
1865 type Error = std::convert::Infallible;
1866
1867 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
1868 let dict = pyo3::types::PyDict::new(py);
1869 dict.set_item(
1870 "path",
1871 (
1872 self.path
1873 .0
1874 .as_ref()
1875 .map(|p| p.to_string_lossy().to_string()),
1876 self.path
1877 .1
1878 .as_ref()
1879 .map(|p| p.to_string_lossy().to_string()),
1880 ),
1881 )
1882 .unwrap();
1883 dict.set_item("changed_content", self.changed_content)
1884 .unwrap();
1885 dict.set_item("versioned", self.versioned).unwrap();
1886 dict.set_item("name", &self.name).unwrap();
1887 dict.set_item("kind", self.kind.clone()).unwrap();
1888 dict.set_item("executable", self.executable).unwrap();
1889 dict.set_item("copied", self.copied).unwrap();
1890 Ok(dict.into_any())
1891 }
1892}
1893
1894impl<'a, 'py> FromPyObject<'a, 'py> for TreeChange {
1895 type Error = PyErr;
1896
1897 fn extract(obj: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1898 fn from_bool(o: &Bound<PyAny>) -> PyResult<bool> {
1899 if let Ok(b) = o.extract::<isize>() {
1900 Ok(b != 0)
1901 } else {
1902 o.extract::<bool>()
1903 }
1904 }
1905
1906 fn from_opt_bool_tuple(o: &Bound<PyAny>) -> PyResult<(Option<bool>, Option<bool>)> {
1907 let tuple = o.extract::<(Option<Bound<PyAny>>, Option<Bound<PyAny>>)>()?;
1908 Ok((
1909 tuple.0.map(|o| from_bool(&o.as_borrowed())).transpose()?,
1910 tuple.1.map(|o| from_bool(&o.as_borrowed())).transpose()?,
1911 ))
1912 }
1913
1914 let path = obj.getattr("path")?;
1915 let changed_content = from_bool(&obj.getattr("changed_content")?)?;
1916
1917 let versioned = from_opt_bool_tuple(&obj.getattr("versioned")?)?;
1918 let name = obj.getattr("name")?;
1919 let kind = obj.getattr("kind")?;
1920 let executable = from_opt_bool_tuple(&obj.getattr("executable")?)?;
1921 let copied = obj.getattr("copied")?;
1922
1923 Ok(TreeChange {
1924 path: path.extract()?,
1925 changed_content,
1926 versioned,
1927 name: name.extract()?,
1928 kind: kind.extract()?,
1929 executable,
1930 copied: copied.extract()?,
1931 })
1932 }
1933}
1934
1935pub struct MemoryTree(pub Py<PyAny>);
1937
1938impl<'py> IntoPyObject<'py> for MemoryTree {
1939 type Target = PyAny;
1940 type Output = Bound<'py, Self::Target>;
1941 type Error = std::convert::Infallible;
1942
1943 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
1944 Ok(self.0.into_bound(py))
1945 }
1946}
1947
1948impl<B: crate::branch::PyBranch> From<&B> for MemoryTree {
1949 fn from(branch: &B) -> Self {
1950 Python::attach(|py| {
1951 MemoryTree(
1952 branch
1953 .to_object(py)
1954 .call_method0(py, "create_memorytree")
1955 .unwrap()
1956 .extract(py)
1957 .unwrap(),
1958 )
1959 })
1960 }
1961}
1962
1963impl PyTree for MemoryTree {
1964 fn to_object(&self, py: Python) -> Py<PyAny> {
1965 self.0.clone_ref(py)
1966 }
1967}
1968
1969impl PyMutableTree for MemoryTree {}
1970
1971pub use crate::workingtree::WorkingTree;
1972
1973#[cfg(test)]
1974mod tests {
1975 use super::*;
1976 use crate::controldir::{create_standalone_workingtree, ControlDirFormat};
1977 use serial_test::serial;
1978
1979 #[test]
1980 #[serial]
1981 fn test_remove() {
1982 let env = crate::testing::TestEnv::new();
1983 let wt =
1984 create_standalone_workingtree(std::path::Path::new("."), &ControlDirFormat::default())
1985 .unwrap();
1986 let path = std::path::Path::new("foo");
1987 std::fs::write(&path, b"").unwrap();
1988 wt.add(&[(std::path::Path::new("foo"))]).unwrap();
1989 wt.build_commit()
1990 .message("Initial commit")
1991 .reporter(&crate::commit::NullCommitReporter::new())
1992 .commit()
1993 .unwrap();
1994 assert!(wt.has_filename(&path));
1995 wt.remove(&[Path::new("foo")]).unwrap();
1996 assert!(!wt.is_versioned(&path));
1997 std::mem::drop(env);
1998 }
1999
2000 #[test]
2001 #[serial]
2002 fn test_walkdirs() {
2003 let env = crate::testing::TestEnv::new();
2004 let wt =
2005 create_standalone_workingtree(std::path::Path::new("."), &ControlDirFormat::default())
2006 .unwrap();
2007
2008 std::fs::create_dir("subdir").unwrap();
2010 std::fs::write("file1.txt", b"content1").unwrap();
2011 std::fs::write("subdir/file2.txt", b"content2").unwrap();
2012 std::fs::write(".gitattributes", b"* text=auto\n").unwrap();
2013
2014 wt.add(&[
2015 Path::new("file1.txt"),
2016 Path::new("subdir"),
2017 Path::new("subdir/file2.txt"),
2018 Path::new(".gitattributes"),
2019 ])
2020 .unwrap();
2021
2022 wt.build_commit()
2023 .message("Add files")
2024 .reporter(&crate::commit::NullCommitReporter::new())
2025 .commit()
2026 .unwrap();
2027
2028 let lock = wt.lock_read().unwrap();
2029
2030 let entries: Vec<_> = wt.walkdirs(None).unwrap().collect();
2032 assert!(entries.len() > 0, "Should have at least some entries");
2033
2034 let found_gitattributes = entries.iter().any(|entry| {
2036 entry
2037 .as_ref()
2038 .map(|e| e.relpath.file_name() == Some(std::ffi::OsStr::new(".gitattributes")))
2039 .unwrap_or(false)
2040 });
2041 assert!(
2042 found_gitattributes,
2043 "Should find .gitattributes file. Found entries: {:?}",
2044 entries
2045 .iter()
2046 .filter_map(|e| e.as_ref().ok())
2047 .map(|e| &e.relpath)
2048 .collect::<Vec<_>>()
2049 );
2050
2051 let found_subdir_file = entries.iter().any(|entry| {
2053 entry
2054 .as_ref()
2055 .map(|e| e.relpath.to_str() == Some("subdir/file2.txt"))
2056 .unwrap_or(false)
2057 });
2058 assert!(found_subdir_file, "Should find file in subdirectory");
2059
2060 std::mem::drop(lock);
2061 std::mem::drop(env);
2062 }
2063}