1use std::borrow::Borrow;
19use std::cmp::Ordering;
20use std::collections::HashMap;
21use std::fmt;
22use std::fmt::Debug;
23use std::fmt::Formatter;
24use std::iter;
25use std::iter::FusedIterator;
26use std::ops::Deref;
27use std::path::Component;
28use std::path::Path;
29use std::path::PathBuf;
30
31use itertools::Itertools as _;
32use ref_cast::RefCastCustom;
33use ref_cast::ref_cast_custom;
34use thiserror::Error;
35
36use crate::content_hash::ContentHash;
37
38#[derive(ContentHash, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
40pub struct RepoPathComponentBuf {
41 value: String,
44}
45
46impl RepoPathComponentBuf {
47 pub fn new(value: impl Into<String>) -> Result<Self, InvalidNewRepoPathError> {
52 let value: String = value.into();
53 if is_valid_repo_path_component_str(&value) {
54 Ok(Self { value })
55 } else {
56 Err(InvalidNewRepoPathError { value })
57 }
58 }
59}
60
61#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, RefCastCustom)]
63#[repr(transparent)]
64pub struct RepoPathComponent {
65 value: str,
66}
67
68impl RepoPathComponent {
69 pub fn new(value: &str) -> Result<&Self, InvalidNewRepoPathError> {
74 if is_valid_repo_path_component_str(value) {
75 Ok(Self::new_unchecked(value))
76 } else {
77 Err(InvalidNewRepoPathError {
78 value: value.to_string(),
79 })
80 }
81 }
82
83 #[ref_cast_custom]
84 const fn new_unchecked(value: &str) -> &Self;
85
86 pub fn as_internal_str(&self) -> &str {
88 &self.value
89 }
90
91 pub fn to_fs_name(&self) -> Result<&str, InvalidRepoPathComponentError> {
94 let mut components = Path::new(&self.value).components().fuse();
95 match (components.next(), components.next()) {
96 (Some(Component::Normal(name)), None) if name == &self.value => Ok(&self.value),
99 _ => Err(InvalidRepoPathComponentError {
101 component: self.value.into(),
102 }),
103 }
104 }
105}
106
107impl Debug for RepoPathComponent {
108 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
109 write!(f, "{:?}", &self.value)
110 }
111}
112
113impl Debug for RepoPathComponentBuf {
114 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
115 <RepoPathComponent as Debug>::fmt(self, f)
116 }
117}
118
119impl AsRef<Self> for RepoPathComponent {
120 fn as_ref(&self) -> &Self {
121 self
122 }
123}
124
125impl AsRef<RepoPathComponent> for RepoPathComponentBuf {
126 fn as_ref(&self) -> &RepoPathComponent {
127 self
128 }
129}
130
131impl Borrow<RepoPathComponent> for RepoPathComponentBuf {
132 fn borrow(&self) -> &RepoPathComponent {
133 self
134 }
135}
136
137impl Deref for RepoPathComponentBuf {
138 type Target = RepoPathComponent;
139
140 fn deref(&self) -> &Self::Target {
141 RepoPathComponent::new_unchecked(&self.value)
142 }
143}
144
145impl ToOwned for RepoPathComponent {
146 type Owned = RepoPathComponentBuf;
147
148 fn to_owned(&self) -> Self::Owned {
149 let value = self.value.to_owned();
150 RepoPathComponentBuf { value }
151 }
152
153 fn clone_into(&self, target: &mut Self::Owned) {
154 self.value.clone_into(&mut target.value);
155 }
156}
157
158#[derive(Clone, Debug)]
160pub struct RepoPathComponentsIter<'a> {
161 value: &'a str,
162}
163
164impl<'a> RepoPathComponentsIter<'a> {
165 pub fn as_path(&self) -> &'a RepoPath {
167 RepoPath::from_internal_string_unchecked(self.value)
168 }
169}
170
171impl<'a> Iterator for RepoPathComponentsIter<'a> {
172 type Item = &'a RepoPathComponent;
173
174 fn next(&mut self) -> Option<Self::Item> {
175 if self.value.is_empty() {
176 return None;
177 }
178 let (name, remainder) = self
179 .value
180 .split_once('/')
181 .unwrap_or_else(|| (self.value, &self.value[self.value.len()..]));
182 self.value = remainder;
183 Some(RepoPathComponent::new_unchecked(name))
184 }
185}
186
187impl DoubleEndedIterator for RepoPathComponentsIter<'_> {
188 fn next_back(&mut self) -> Option<Self::Item> {
189 if self.value.is_empty() {
190 return None;
191 }
192 let (remainder, name) = self
193 .value
194 .rsplit_once('/')
195 .unwrap_or_else(|| (&self.value[..0], self.value));
196 self.value = remainder;
197 Some(RepoPathComponent::new_unchecked(name))
198 }
199}
200
201impl FusedIterator for RepoPathComponentsIter<'_> {}
202
203#[derive(ContentHash, Clone, Eq, Hash, PartialEq, serde::Serialize)]
205#[serde(transparent)]
206pub struct RepoPathBuf {
207 value: String,
210}
211
212#[derive(ContentHash, Eq, Hash, PartialEq, RefCastCustom, serde::Serialize)]
214#[repr(transparent)]
215#[serde(transparent)]
216pub struct RepoPath {
217 value: str,
218}
219
220impl Debug for RepoPath {
221 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
222 write!(f, "{:?}", &self.value)
223 }
224}
225
226impl Debug for RepoPathBuf {
227 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
228 <RepoPath as Debug>::fmt(self, f)
229 }
230}
231
232#[derive(Clone, Debug, Eq, Error, PartialEq)]
236#[error(r#"Invalid repo path input "{value}""#)]
237pub struct InvalidNewRepoPathError {
238 value: String,
239}
240
241impl RepoPathBuf {
242 pub const fn root() -> Self {
244 Self {
245 value: String::new(),
246 }
247 }
248
249 pub fn from_internal_string(value: impl Into<String>) -> Result<Self, InvalidNewRepoPathError> {
251 let value: String = value.into();
252 if is_valid_repo_path_str(&value) {
253 Ok(Self { value })
254 } else {
255 Err(InvalidNewRepoPathError { value })
256 }
257 }
258
259 pub fn from_relative_path(
263 relative_path: impl AsRef<Path>,
264 ) -> Result<Self, RelativePathParseError> {
265 let relative_path = relative_path.as_ref();
266 if relative_path == Path::new(".") {
267 return Ok(Self::root());
268 }
269
270 let mut components = relative_path
271 .components()
272 .map(|c| match c {
273 Component::Normal(name) => {
274 name.to_str()
275 .ok_or_else(|| RelativePathParseError::InvalidUtf8 {
276 path: relative_path.into(),
277 })
278 }
279 _ => Err(RelativePathParseError::InvalidComponent {
280 component: c.as_os_str().to_string_lossy().into(),
281 path: relative_path.into(),
282 }),
283 })
284 .fuse();
285 let mut value = String::with_capacity(relative_path.as_os_str().len());
286 if let Some(name) = components.next() {
287 value.push_str(name?);
288 }
289 for name in components {
290 value.push('/');
291 value.push_str(name?);
292 }
293 Ok(Self { value })
294 }
295
296 pub fn into_internal_string(self) -> String {
298 self.value
299 }
300}
301
302impl RepoPath {
303 pub const fn root() -> &'static Self {
305 Self::from_internal_string_unchecked("")
306 }
307
308 pub fn from_internal_string(value: &str) -> Result<&Self, InvalidNewRepoPathError> {
313 if is_valid_repo_path_str(value) {
314 Ok(Self::from_internal_string_unchecked(value))
315 } else {
316 Err(InvalidNewRepoPathError {
317 value: value.to_owned(),
318 })
319 }
320 }
321
322 #[ref_cast_custom]
323 const fn from_internal_string_unchecked(value: &str) -> &Self;
324
325 pub fn to_internal_dir_string(&self) -> String {
330 if self.value.is_empty() {
331 String::new()
332 } else {
333 [&self.value, "/"].concat()
334 }
335 }
336
337 pub fn as_internal_file_string(&self) -> &str {
340 &self.value
341 }
342
343 pub fn to_fs_path(&self, base: &Path) -> Result<PathBuf, InvalidRepoPathError> {
348 let mut result = PathBuf::with_capacity(base.as_os_str().len() + self.value.len() + 1);
349 result.push(base);
350 for c in self.components() {
351 result.push(c.to_fs_name().map_err(|err| err.with_path(self))?);
352 }
353 if result.as_os_str().is_empty() {
354 result.push(".");
355 }
356 Ok(result)
357 }
358
359 pub fn to_fs_path_unchecked(&self, base: &Path) -> PathBuf {
365 let mut result = PathBuf::with_capacity(base.as_os_str().len() + self.value.len() + 1);
366 result.push(base);
367 result.extend(self.components().map(RepoPathComponent::as_internal_str));
368 if result.as_os_str().is_empty() {
369 result.push(".");
370 }
371 result
372 }
373
374 pub fn is_root(&self) -> bool {
376 self.value.is_empty()
377 }
378
379 pub fn starts_with(&self, base: &Self) -> bool {
381 self.strip_prefix(base).is_some()
382 }
383
384 pub fn strip_prefix(&self, base: &Self) -> Option<&Self> {
386 if base.value.is_empty() {
387 Some(self)
388 } else {
389 let tail = self.value.strip_prefix(&base.value)?;
390 if tail.is_empty() {
391 Some(Self::from_internal_string_unchecked(tail))
392 } else {
393 tail.strip_prefix('/')
394 .map(Self::from_internal_string_unchecked)
395 }
396 }
397 }
398
399 pub fn parent(&self) -> Option<&Self> {
401 self.split().map(|(parent, _)| parent)
402 }
403
404 pub fn split(&self) -> Option<(&Self, &RepoPathComponent)> {
406 let mut components = self.components();
407 let basename = components.next_back()?;
408 Some((components.as_path(), basename))
409 }
410
411 pub fn components(&self) -> RepoPathComponentsIter<'_> {
416 RepoPathComponentsIter { value: &self.value }
417 }
418
419 pub fn ancestors(&self) -> impl Iterator<Item = &Self> {
424 std::iter::successors(Some(self), |path| path.parent())
425 }
426
427 pub fn join(&self, entry: &RepoPathComponent) -> RepoPathBuf {
429 let value = if self.value.is_empty() {
430 entry.as_internal_str().to_owned()
431 } else {
432 [&self.value, "/", entry.as_internal_str()].concat()
433 };
434 RepoPathBuf { value }
435 }
436
437 pub fn split_common_prefix(&self, other: &Self) -> (&Self, &Self) {
471 let mut prefix_len = 0;
473
474 let common_components = self
475 .components()
476 .zip(other.components())
477 .take_while(|(prev_comp, this_comp)| prev_comp == this_comp);
478
479 for (self_comp, _other_comp) in common_components {
480 if prefix_len > 0 {
481 prefix_len += 1;
485 }
486
487 prefix_len += self_comp.value.len();
488 }
489
490 if prefix_len == 0 {
491 return (Self::root(), self);
493 }
494
495 if prefix_len == self.value.len() {
496 return (self, Self::root());
497 }
498
499 let common_prefix = Self::from_internal_string_unchecked(&self.value[..prefix_len]);
500 let remainder = Self::from_internal_string_unchecked(&self.value[prefix_len + 1..]);
501
502 (common_prefix, remainder)
503 }
504}
505
506impl AsRef<Self> for RepoPath {
507 fn as_ref(&self) -> &Self {
508 self
509 }
510}
511
512impl AsRef<RepoPath> for RepoPathBuf {
513 fn as_ref(&self) -> &RepoPath {
514 self
515 }
516}
517
518impl Borrow<RepoPath> for RepoPathBuf {
519 fn borrow(&self) -> &RepoPath {
520 self
521 }
522}
523
524impl Deref for RepoPathBuf {
525 type Target = RepoPath;
526
527 fn deref(&self) -> &Self::Target {
528 RepoPath::from_internal_string_unchecked(&self.value)
529 }
530}
531
532impl ToOwned for RepoPath {
533 type Owned = RepoPathBuf;
534
535 fn to_owned(&self) -> Self::Owned {
536 let value = self.value.to_owned();
537 RepoPathBuf { value }
538 }
539
540 fn clone_into(&self, target: &mut Self::Owned) {
541 self.value.clone_into(&mut target.value);
542 }
543}
544
545impl Ord for RepoPath {
546 fn cmp(&self, other: &Self) -> Ordering {
547 debug_assert!(is_valid_repo_path_str(&self.value));
550 self.components().cmp(other.components())
551 }
552}
553
554impl Ord for RepoPathBuf {
555 fn cmp(&self, other: &Self) -> Ordering {
556 <RepoPath as Ord>::cmp(self, other)
557 }
558}
559
560impl PartialOrd for RepoPath {
561 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
562 Some(self.cmp(other))
563 }
564}
565
566impl PartialOrd for RepoPathBuf {
567 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
568 Some(self.cmp(other))
569 }
570}
571
572impl<P: AsRef<RepoPathComponent>> Extend<P> for RepoPathBuf {
573 fn extend<T: IntoIterator<Item = P>>(&mut self, iter: T) {
574 for component in iter {
575 if !self.value.is_empty() {
576 self.value.push('/');
577 }
578 self.value.push_str(component.as_ref().as_internal_str());
579 }
580 }
581}
582
583#[derive(Clone, Debug, Eq, Error, PartialEq)]
585#[error(r#"Invalid repository path "{}""#, path.as_internal_file_string())]
586pub struct InvalidRepoPathError {
587 pub path: RepoPathBuf,
589 pub source: InvalidRepoPathComponentError,
591}
592
593#[derive(Clone, Debug, Eq, Error, PartialEq)]
595#[error(r#"Invalid path component "{component}""#)]
596pub struct InvalidRepoPathComponentError {
597 pub component: Box<str>,
599}
600
601impl InvalidRepoPathComponentError {
602 pub fn with_path(self, path: &RepoPath) -> InvalidRepoPathError {
604 InvalidRepoPathError {
605 path: path.to_owned(),
606 source: self,
607 }
608 }
609}
610
611#[derive(Clone, Debug, Eq, Error, PartialEq)]
613pub enum RelativePathParseError {
614 #[error(r#"Invalid component "{component}" in repo-relative path "{path}""#)]
616 InvalidComponent {
617 component: Box<str>,
619 path: Box<Path>,
621 },
622 #[error(r#"Not valid UTF-8 path "{path}""#)]
624 InvalidUtf8 {
625 path: Box<Path>,
627 },
628}
629
630fn is_valid_repo_path_component_str(value: &str) -> bool {
631 !value.is_empty() && !value.contains('/')
632}
633
634fn is_valid_repo_path_str(value: &str) -> bool {
635 !value.starts_with('/') && !value.ends_with('/') && !value.contains("//")
636}
637
638#[derive(Clone, Default, Eq, PartialEq)]
640pub struct RepoPathTree<V> {
641 entries: HashMap<RepoPathComponentBuf, Self>,
642 value: V,
643}
644
645impl<V> RepoPathTree<V> {
646 pub fn value(&self) -> &V {
648 &self.value
649 }
650
651 pub fn value_mut(&mut self) -> &mut V {
653 &mut self.value
654 }
655
656 pub fn set_value(&mut self, value: V) {
658 self.value = value;
659 }
660
661 pub fn children(&self) -> impl Iterator<Item = (&RepoPathComponent, &Self)> {
663 self.entries
664 .iter()
665 .map(|(component, value)| (component.as_ref(), value))
666 }
667
668 pub fn has_children(&self) -> bool {
670 !self.entries.is_empty()
671 }
672
673 pub fn add(&mut self, path: &RepoPath) -> &mut Self
675 where
676 V: Default,
677 {
678 path.components().fold(self, |sub, name| {
679 if !sub.entries.contains_key(name) {
681 sub.entries.insert(name.to_owned(), Self::default());
682 }
683 sub.entries.get_mut(name).unwrap()
684 })
685 }
686
687 pub fn get(&self, path: &RepoPath) -> Option<&Self> {
690 path.components()
691 .try_fold(self, |sub, name| sub.entries.get(name))
692 }
693
694 pub fn walk_to<'a, 'b>(
697 &'a self,
698 path: &'b RepoPath,
699 ) -> impl Iterator<Item = (&'a Self, &'b RepoPath)> {
700 iter::successors(Some((self, path)), |(sub, path)| {
701 let mut components = path.components();
702 let name = components.next()?;
703 Some((sub.entries.get(name)?, components.as_path()))
704 })
705 }
706}
707
708impl<V: Debug> Debug for RepoPathTree<V> {
709 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
710 self.value.fmt(f)?;
711 f.write_str(" ")?;
712 f.debug_map()
713 .entries(
714 self.entries
715 .iter()
716 .sorted_unstable_by_key(|&(name, _)| name),
717 )
718 .finish()
719 }
720}
721
722#[cfg(test)]
723mod tests {
724 use std::panic;
725
726 use super::*;
727
728 fn repo_path(value: &str) -> &RepoPath {
729 RepoPath::from_internal_string(value).unwrap()
730 }
731
732 fn repo_path_component(value: &str) -> &RepoPathComponent {
733 RepoPathComponent::new(value).unwrap()
734 }
735
736 #[test]
737 fn test_is_root() {
738 assert!(RepoPath::root().is_root());
739 assert!(repo_path("").is_root());
740 assert!(!repo_path("foo").is_root());
741 }
742
743 #[test]
744 fn test_from_internal_string() {
745 let repo_path_buf = |value: &str| RepoPathBuf::from_internal_string(value).unwrap();
746 assert_eq!(repo_path_buf(""), RepoPathBuf::root());
747 assert!(panic::catch_unwind(|| repo_path_buf("/")).is_err());
748 assert!(panic::catch_unwind(|| repo_path_buf("/x")).is_err());
749 assert!(panic::catch_unwind(|| repo_path_buf("x/")).is_err());
750 assert!(panic::catch_unwind(|| repo_path_buf("x//y")).is_err());
751
752 assert_eq!(repo_path(""), RepoPath::root());
753 assert!(panic::catch_unwind(|| repo_path("/")).is_err());
754 assert!(panic::catch_unwind(|| repo_path("/x")).is_err());
755 assert!(panic::catch_unwind(|| repo_path("x/")).is_err());
756 assert!(panic::catch_unwind(|| repo_path("x//y")).is_err());
757 }
758
759 #[test]
760 fn test_as_internal_file_string() {
761 assert_eq!(RepoPath::root().as_internal_file_string(), "");
762 assert_eq!(repo_path("dir").as_internal_file_string(), "dir");
763 assert_eq!(repo_path("dir/file").as_internal_file_string(), "dir/file");
764 }
765
766 #[test]
767 fn test_to_internal_dir_string() {
768 assert_eq!(RepoPath::root().to_internal_dir_string(), "");
769 assert_eq!(repo_path("dir").to_internal_dir_string(), "dir/");
770 assert_eq!(repo_path("dir/file").to_internal_dir_string(), "dir/file/");
771 }
772
773 #[test]
774 fn test_starts_with() {
775 assert!(repo_path("").starts_with(repo_path("")));
776 assert!(repo_path("x").starts_with(repo_path("")));
777 assert!(!repo_path("").starts_with(repo_path("x")));
778
779 assert!(repo_path("x").starts_with(repo_path("x")));
780 assert!(repo_path("x/y").starts_with(repo_path("x")));
781 assert!(!repo_path("xy").starts_with(repo_path("x")));
782 assert!(!repo_path("x/y").starts_with(repo_path("y")));
783
784 assert!(repo_path("x/y").starts_with(repo_path("x/y")));
785 assert!(repo_path("x/y/z").starts_with(repo_path("x/y")));
786 assert!(!repo_path("x/yz").starts_with(repo_path("x/y")));
787 assert!(!repo_path("x").starts_with(repo_path("x/y")));
788 assert!(!repo_path("xy").starts_with(repo_path("x/y")));
789 }
790
791 #[test]
792 fn test_strip_prefix() {
793 assert_eq!(
794 repo_path("").strip_prefix(repo_path("")),
795 Some(repo_path(""))
796 );
797 assert_eq!(
798 repo_path("x").strip_prefix(repo_path("")),
799 Some(repo_path("x"))
800 );
801 assert_eq!(repo_path("").strip_prefix(repo_path("x")), None);
802
803 assert_eq!(
804 repo_path("x").strip_prefix(repo_path("x")),
805 Some(repo_path(""))
806 );
807 assert_eq!(
808 repo_path("x/y").strip_prefix(repo_path("x")),
809 Some(repo_path("y"))
810 );
811 assert_eq!(repo_path("xy").strip_prefix(repo_path("x")), None);
812 assert_eq!(repo_path("x/y").strip_prefix(repo_path("y")), None);
813
814 assert_eq!(
815 repo_path("x/y").strip_prefix(repo_path("x/y")),
816 Some(repo_path(""))
817 );
818 assert_eq!(
819 repo_path("x/y/z").strip_prefix(repo_path("x/y")),
820 Some(repo_path("z"))
821 );
822 assert_eq!(repo_path("x/yz").strip_prefix(repo_path("x/y")), None);
823 assert_eq!(repo_path("x").strip_prefix(repo_path("x/y")), None);
824 assert_eq!(repo_path("xy").strip_prefix(repo_path("x/y")), None);
825 }
826
827 #[test]
828 fn test_order() {
829 assert!(RepoPath::root() < repo_path("dir"));
830 assert!(repo_path("dir") < repo_path("dirx"));
831 assert!(repo_path("dir") < repo_path("dir#"));
833 assert!(repo_path("dir") < repo_path("dir/sub"));
834 assert!(repo_path("dir/sub") < repo_path("dir#"));
835
836 assert!(repo_path("abc") < repo_path("dir/file"));
837 assert!(repo_path("dir") < repo_path("dir/file"));
838 assert!(repo_path("dis") > repo_path("dir/file"));
839 assert!(repo_path("xyz") > repo_path("dir/file"));
840 assert!(repo_path("dir1/xyz") < repo_path("dir2/abc"));
841 }
842
843 #[test]
844 fn test_join() {
845 let root = RepoPath::root();
846 let dir = root.join(repo_path_component("dir"));
847 assert_eq!(dir.as_ref(), repo_path("dir"));
848 let subdir = dir.join(repo_path_component("subdir"));
849 assert_eq!(subdir.as_ref(), repo_path("dir/subdir"));
850 assert_eq!(
851 subdir.join(repo_path_component("file")).as_ref(),
852 repo_path("dir/subdir/file")
853 );
854 }
855
856 #[test]
857 fn test_extend() {
858 let mut path = RepoPathBuf::root();
859 path.extend(std::iter::empty::<RepoPathComponentBuf>());
860 assert_eq!(path.as_ref(), RepoPath::root());
861 path.extend([repo_path_component("dir")]);
862 assert_eq!(path.as_ref(), repo_path("dir"));
863 path.extend(std::iter::repeat_n(repo_path_component("subdir"), 3));
864 assert_eq!(path.as_ref(), repo_path("dir/subdir/subdir/subdir"));
865 path.extend(std::iter::empty::<RepoPathComponentBuf>());
866 assert_eq!(path.as_ref(), repo_path("dir/subdir/subdir/subdir"));
867 }
868
869 #[test]
870 fn test_parent() {
871 let root = RepoPath::root();
872 let dir_component = repo_path_component("dir");
873 let subdir_component = repo_path_component("subdir");
874
875 let dir = root.join(dir_component);
876 let subdir = dir.join(subdir_component);
877
878 assert_eq!(root.parent(), None);
879 assert_eq!(dir.parent(), Some(root));
880 assert_eq!(subdir.parent(), Some(dir.as_ref()));
881 }
882
883 #[test]
884 fn test_split() {
885 let root = RepoPath::root();
886 let dir_component = repo_path_component("dir");
887 let file_component = repo_path_component("file");
888
889 let dir = root.join(dir_component);
890 let file = dir.join(file_component);
891
892 assert_eq!(root.split(), None);
893 assert_eq!(dir.split(), Some((root, dir_component)));
894 assert_eq!(file.split(), Some((dir.as_ref(), file_component)));
895 }
896
897 #[test]
898 fn test_components() {
899 assert!(RepoPath::root().components().next().is_none());
900 assert_eq!(
901 repo_path("dir").components().collect_vec(),
902 vec![repo_path_component("dir")]
903 );
904 assert_eq!(
905 repo_path("dir/subdir").components().collect_vec(),
906 vec![repo_path_component("dir"), repo_path_component("subdir")]
907 );
908
909 assert!(RepoPath::root().components().next_back().is_none());
911 assert_eq!(
912 repo_path("dir").components().rev().collect_vec(),
913 vec![repo_path_component("dir")]
914 );
915 assert_eq!(
916 repo_path("dir/subdir").components().rev().collect_vec(),
917 vec![repo_path_component("subdir"), repo_path_component("dir")]
918 );
919 }
920
921 #[test]
922 fn test_ancestors() {
923 assert_eq!(
924 RepoPath::root().ancestors().collect_vec(),
925 vec![RepoPath::root()]
926 );
927 assert_eq!(
928 repo_path("dir").ancestors().collect_vec(),
929 vec![repo_path("dir"), RepoPath::root()]
930 );
931 assert_eq!(
932 repo_path("dir/subdir").ancestors().collect_vec(),
933 vec![repo_path("dir/subdir"), repo_path("dir"), RepoPath::root()]
934 );
935 }
936
937 #[test]
938 fn test_to_fs_path() {
939 assert_eq!(
940 repo_path("").to_fs_path(Path::new("base/dir")).unwrap(),
941 Path::new("base/dir")
942 );
943 assert_eq!(
944 repo_path("").to_fs_path(Path::new("")).unwrap(),
945 Path::new(".")
946 );
947 assert_eq!(
948 repo_path("file").to_fs_path(Path::new("base/dir")).unwrap(),
949 Path::new("base/dir/file")
950 );
951 assert_eq!(
952 repo_path("some/deep/dir/file")
953 .to_fs_path(Path::new("base/dir"))
954 .unwrap(),
955 Path::new("base/dir/some/deep/dir/file")
956 );
957 assert_eq!(
958 repo_path("dir/file").to_fs_path(Path::new("")).unwrap(),
959 Path::new("dir/file")
960 );
961
962 assert!(repo_path(".").to_fs_path(Path::new("base")).is_err());
964 assert!(repo_path("..").to_fs_path(Path::new("base")).is_err());
965 assert!(
966 repo_path("dir/../file")
967 .to_fs_path(Path::new("base"))
968 .is_err()
969 );
970 assert!(repo_path("./file").to_fs_path(Path::new("base")).is_err());
971 assert!(repo_path("file/.").to_fs_path(Path::new("base")).is_err());
972 assert!(repo_path("../file").to_fs_path(Path::new("base")).is_err());
973 assert!(repo_path("file/..").to_fs_path(Path::new("base")).is_err());
974
975 assert!(
977 RepoPath::from_internal_string_unchecked("/")
978 .to_fs_path(Path::new("base"))
979 .is_err()
980 );
981 assert_eq!(
982 RepoPath::from_internal_string_unchecked("a/")
985 .to_fs_path(Path::new("base"))
986 .unwrap(),
987 Path::new("base/a")
988 );
989 assert!(
990 RepoPath::from_internal_string_unchecked("/b")
991 .to_fs_path(Path::new("base"))
992 .is_err()
993 );
994 assert!(
995 RepoPath::from_internal_string_unchecked("a//b")
996 .to_fs_path(Path::new("base"))
997 .is_err()
998 );
999
1000 assert!(
1002 RepoPathComponent::new_unchecked("wind/ows")
1003 .to_fs_name()
1004 .is_err()
1005 );
1006 assert!(
1007 RepoPathComponent::new_unchecked("./file")
1008 .to_fs_name()
1009 .is_err()
1010 );
1011 assert!(
1012 RepoPathComponent::new_unchecked("file/.")
1013 .to_fs_name()
1014 .is_err()
1015 );
1016 assert!(RepoPathComponent::new_unchecked("/").to_fs_name().is_err());
1017
1018 if cfg!(windows) {
1020 assert!(
1021 repo_path(r#"wind\ows"#)
1022 .to_fs_path(Path::new("base"))
1023 .is_err()
1024 );
1025 assert!(
1026 repo_path(r#".\file"#)
1027 .to_fs_path(Path::new("base"))
1028 .is_err()
1029 );
1030 assert!(
1031 repo_path(r#"file\."#)
1032 .to_fs_path(Path::new("base"))
1033 .is_err()
1034 );
1035 assert!(
1036 repo_path(r#"c:/foo"#)
1037 .to_fs_path(Path::new("base"))
1038 .is_err()
1039 );
1040 }
1041 }
1042
1043 #[test]
1044 fn test_to_fs_path_unchecked() {
1045 assert_eq!(
1046 repo_path("").to_fs_path_unchecked(Path::new("base/dir")),
1047 Path::new("base/dir")
1048 );
1049 assert_eq!(
1050 repo_path("").to_fs_path_unchecked(Path::new("")),
1051 Path::new(".")
1052 );
1053 assert_eq!(
1054 repo_path("file").to_fs_path_unchecked(Path::new("base/dir")),
1055 Path::new("base/dir/file")
1056 );
1057 assert_eq!(
1058 repo_path("some/deep/dir/file").to_fs_path_unchecked(Path::new("base/dir")),
1059 Path::new("base/dir/some/deep/dir/file")
1060 );
1061 assert_eq!(
1062 repo_path("dir/file").to_fs_path_unchecked(Path::new("")),
1063 Path::new("dir/file")
1064 );
1065 }
1066
1067 #[test]
1068 fn test_split_common_prefix() {
1069 assert_eq!(
1070 repo_path("foo/bar").split_common_prefix(repo_path("foo/bar/baz")),
1071 (repo_path("foo/bar"), repo_path(""))
1072 );
1073
1074 assert_eq!(
1075 repo_path("foo/bar/baz").split_common_prefix(repo_path("foo/bar")),
1076 (repo_path("foo/bar"), repo_path("baz"))
1077 );
1078
1079 assert_eq!(
1080 repo_path("foo/bar/bing").split_common_prefix(repo_path("foo/bar/baz")),
1081 (repo_path("foo/bar"), repo_path("bing"))
1082 );
1083
1084 assert_eq!(
1085 repo_path("no/common/prefix").split_common_prefix(repo_path("foo/bar/baz")),
1086 (RepoPath::root(), repo_path("no/common/prefix"))
1087 );
1088
1089 assert_eq!(
1090 repo_path("same/path").split_common_prefix(repo_path("same/path")),
1091 (repo_path("same/path"), RepoPath::root())
1092 );
1093
1094 assert_eq!(
1095 RepoPath::root().split_common_prefix(repo_path("foo")),
1096 (RepoPath::root(), RepoPath::root())
1097 );
1098
1099 assert_eq!(
1100 RepoPath::root().split_common_prefix(RepoPath::root()),
1101 (RepoPath::root(), RepoPath::root())
1102 );
1103
1104 assert_eq!(
1105 repo_path("foo/bar").split_common_prefix(RepoPath::root()),
1106 (RepoPath::root(), repo_path("foo/bar"))
1107 );
1108 }
1109}