// Copyright 2020 The Jujutsu Authors//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// https://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License. #![allow(missing_docs)] use std::borrow::Borrow;use std::cmp::Ordering;use std::fmt;use std::fmt::Debug;use std::fmt::Formatter;use std::iter::FusedIterator;use std::ops::Deref;use std::path::Component;use std::path::Path;use std::path::PathBuf; use itertools::Itertools as _;use ref_cast::ref_cast_custom;use ref_cast::RefCastCustom;use thiserror::Error; use crate::content_hash::ContentHash;use crate::file_util; /// Owned `RepoPath` component.#[derive(ContentHash, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]pub struct RepoPathComponentBuf { // Don't add more fields. Eq, Hash, and Ord must be compatible with the // borrowed RepoPathComponent type. value: String,} impl RepoPathComponentBuf { /// Wraps `value` as `RepoPathComponentBuf`. /// /// Returns an error if the input `value` is empty or contains path /// separator. pub fn new(value: impl Into<String>) -> Result<Self, InvalidNewRepoPathError> { let value: String = value.into(); if is_valid_repo_path_component_str(&value) { Ok(Self { value }) } else { Err(InvalidNewRepoPathError { value }) } }} /// Borrowed `RepoPath` component.#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, RefCastCustom)]#[repr(transparent)]pub struct RepoPathComponent { value: str,} impl RepoPathComponent { /// Wraps `value` as `RepoPathComponent`. /// /// Returns an error if the input `value` is empty or contains path /// separator. pub fn new(value: &str) -> Result<&Self, InvalidNewRepoPathError> { if is_valid_repo_path_component_str(value) { Ok(Self::new_unchecked(value)) } else { Err(InvalidNewRepoPathError { value: value.to_string(), }) } } #[ref_cast_custom] const fn new_unchecked(value: &str) -> &Self; /// Returns the underlying string representation. pub fn as_internal_str(&self) -> &str { &self.value } /// Returns a normal filesystem entry name if this path component is valid /// as a file/directory name. pub fn to_fs_name(&self) -> Result<&str, InvalidRepoPathComponentError> { let mut components = Path::new(&self.value).components().fuse(); match (components.next(), components.next()) { // Trailing "." can be normalized by Path::components(), so compare // component name. e.g. "foo\." (on Windows) should be rejected. (Some(Component::Normal(name)), None) if name == &self.value => Ok(&self.value), // e.g. ".", "..", "foo\bar" (on Windows) _ => Err(InvalidRepoPathComponentError { component: self.value.into(), }), } }} impl Debug for RepoPathComponent { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{:?}", &self.value) }} impl Debug for RepoPathComponentBuf { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { <RepoPathComponent as Debug>::fmt(self, f) }} impl AsRef<RepoPathComponent> for RepoPathComponent { fn as_ref(&self) -> &RepoPathComponent { self }} impl AsRef<RepoPathComponent> for RepoPathComponentBuf { fn as_ref(&self) -> &RepoPathComponent { self }} impl Borrow<RepoPathComponent> for RepoPathComponentBuf { fn borrow(&self) -> &RepoPathComponent { self }} impl Deref for RepoPathComponentBuf { type Target = RepoPathComponent; fn deref(&self) -> &Self::Target { RepoPathComponent::new_unchecked(&self.value) }} impl ToOwned for RepoPathComponent { type Owned = RepoPathComponentBuf; fn to_owned(&self) -> Self::Owned { let value = self.value.to_owned(); RepoPathComponentBuf { value } } fn clone_into(&self, target: &mut Self::Owned) { self.value.clone_into(&mut target.value); }} /// Iterator over `RepoPath` components.#[derive(Clone, Debug)]pub struct RepoPathComponentsIter<'a> { value: &'a str,} impl<'a> RepoPathComponentsIter<'a> { /// Returns the remaining part as repository path. pub fn as_path(&self) -> &'a RepoPath { RepoPath::from_internal_string_unchecked(self.value) }} impl<'a> Iterator for RepoPathComponentsIter<'a> { type Item = &'a RepoPathComponent; fn next(&mut self) -> Option<Self::Item> { if self.value.is_empty() { return None; } let (name, remainder) = self .value .split_once('/') .unwrap_or_else(|| (self.value, &self.value[self.value.len()..])); self.value = remainder; Some(RepoPathComponent::new_unchecked(name)) }} impl DoubleEndedIterator for RepoPathComponentsIter<'_> { fn next_back(&mut self) -> Option<Self::Item> { if self.value.is_empty() { return None; } let (remainder, name) = self .value .rsplit_once('/') .unwrap_or_else(|| (&self.value[..0], self.value)); self.value = remainder; Some(RepoPathComponent::new_unchecked(name)) }} impl FusedIterator for RepoPathComponentsIter<'_> {} /// Owned repository path.#[derive(ContentHash, Clone, Eq, Hash, PartialEq, serde::Serialize)]#[serde(transparent)]pub struct RepoPathBuf { // Don't add more fields. Eq, Hash, and Ord must be compatible with the // borrowed RepoPath type. value: String,} /// Borrowed repository path.#[derive(ContentHash, Eq, Hash, PartialEq, RefCastCustom, serde::Serialize)]#[repr(transparent)]#[serde(transparent)]pub struct RepoPath { value: str,} impl Debug for RepoPath { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{:?}", &self.value) }} impl Debug for RepoPathBuf { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { <RepoPath as Debug>::fmt(self, f) }} /// The `value` is not a valid repo path because it contains empty path/// component. For example, `"/"`, `"/foo"`, `"foo/"`, `"foo//bar"` are all/// invalid.#[derive(Clone, Debug, Eq, Error, PartialEq)]#[error(r#"Invalid repo path input "{value}""#)]pub struct InvalidNewRepoPathError { value: String,} impl RepoPathBuf { /// Creates owned repository path pointing to the root. pub const fn root() -> Self { RepoPathBuf { value: String::new(), } } /// Creates `RepoPathBuf` from valid string representation. pub fn from_internal_string(value: impl Into<String>) -> Result<Self, InvalidNewRepoPathError> { let value: String = value.into(); if is_valid_repo_path_str(&value) { Ok(RepoPathBuf { value }) } else { Err(InvalidNewRepoPathError { value }) } } /// Converts repo-relative `Path` to `RepoPathBuf`. /// /// The input path should not contain redundant `.` or `..`. pub fn from_relative_path( relative_path: impl AsRef<Path>, ) -> Result<Self, RelativePathParseError> { let relative_path = relative_path.as_ref(); if relative_path == Path::new(".") { return Ok(Self::root()); } let mut components = relative_path .components() .map(|c| match c { Component::Normal(name) => { name.to_str() .ok_or_else(|| RelativePathParseError::InvalidUtf8 { path: relative_path.into(), }) } _ => Err(RelativePathParseError::InvalidComponent { component: c.as_os_str().to_string_lossy().into(), path: relative_path.into(), }), }) .fuse(); let mut value = String::with_capacity(relative_path.as_os_str().len()); if let Some(name) = components.next() { value.push_str(name?); } for name in components { value.push('/'); value.push_str(name?); } Ok(RepoPathBuf { value }) } /// Parses an `input` path into a `RepoPathBuf` relative to `base`. /// /// The `cwd` and `base` paths are supposed to be absolute and normalized in /// the same manner. The `input` path may be either relative to `cwd` or /// absolute. pub fn parse_fs_path( cwd: &Path, base: &Path, input: impl AsRef<Path>, ) -> Result<Self, FsPathParseError> { let input = input.as_ref(); let abs_input_path = file_util::normalize_path(&cwd.join(input)); let repo_relative_path = file_util::relative_path(base, &abs_input_path); Self::from_relative_path(repo_relative_path).map_err(|source| FsPathParseError { base: file_util::relative_path(cwd, base).into(), input: input.into(), source, }) } /// Consumes this and returns the underlying string representation. pub fn into_internal_string(self) -> String { self.value }} impl RepoPath { /// Returns repository path pointing to the root. pub const fn root() -> &'static Self { Self::from_internal_string_unchecked("") } /// Wraps valid string representation as `RepoPath`. /// /// Returns an error if the input `value` contains empty path component. For /// example, `"/"`, `"/foo"`, `"foo/"`, `"foo//bar"` are all invalid. pub fn from_internal_string(value: &str) -> Result<&Self, InvalidNewRepoPathError> { if is_valid_repo_path_str(value) { Ok(Self::from_internal_string_unchecked(value)) } else { Err(InvalidNewRepoPathError { value: value.to_owned(), }) } } #[ref_cast_custom] const fn from_internal_string_unchecked(value: &str) -> &Self; /// The full string form used internally, not for presenting to users (where /// we may want to use the platform's separator). This format includes a /// trailing slash, unless this path represents the root directory. That /// way it can be concatenated with a basename and produce a valid path. pub fn to_internal_dir_string(&self) -> String { if self.value.is_empty() { String::new() } else { [&self.value, "/"].concat() } } /// The full string form used internally, not for presenting to users (where /// we may want to use the platform's separator). pub fn as_internal_file_string(&self) -> &str { &self.value } /// Converts repository path to filesystem path relative to the `base`. /// /// The returned path should never contain `..`, `C:` (on Windows), etc. /// However, it may contain reserved working-copy directories such as `.jj`. pub fn to_fs_path(&self, base: &Path) -> Result<PathBuf, InvalidRepoPathError> { let mut result = PathBuf::with_capacity(base.as_os_str().len() + self.value.len() + 1); result.push(base); for c in self.components() { result.push(c.to_fs_name().map_err(|err| err.with_path(self))?); } if result.as_os_str().is_empty() { result.push("."); } Ok(result) } /// Converts repository path to filesystem path relative to the `base`, /// without checking invalid path components. /// /// The returned path may point outside of the `base` directory. Use this /// function only for displaying or testing purposes. pub fn to_fs_path_unchecked(&self, base: &Path) -> PathBuf { let mut result = PathBuf::with_capacity(base.as_os_str().len() + self.value.len() + 1); result.push(base); result.extend(self.components().map(RepoPathComponent::as_internal_str)); if result.as_os_str().is_empty() { result.push("."); } result } pub fn is_root(&self) -> bool { self.value.is_empty() } /// Returns true if the `base` is a prefix of this path. pub fn starts_with(&self, base: &RepoPath) -> bool { self.strip_prefix(base).is_some() } /// Returns the remaining path with the `base` path removed. pub fn strip_prefix(&self, base: &RepoPath) -> Option<&RepoPath> { if base.value.is_empty() { Some(self) } else { let tail = self.value.strip_prefix(&base.value)?; if tail.is_empty() { Some(RepoPath::from_internal_string_unchecked(tail)) } else { tail.strip_prefix('/') .map(RepoPath::from_internal_string_unchecked) } } } /// Returns the parent path without the base name component. pub fn parent(&self) -> Option<&RepoPath> { self.split().map(|(parent, _)| parent) } /// Splits this into the parent path and base name component. pub fn split(&self) -> Option<(&RepoPath, &RepoPathComponent)> { let mut components = self.components(); let basename = components.next_back()?; Some((components.as_path(), basename)) } pub fn components(&self) -> RepoPathComponentsIter<'_> { RepoPathComponentsIter { value: &self.value } } pub fn ancestors(&self) -> impl Iterator<Item = &RepoPath> { std::iter::successors(Some(self), |path| path.parent()) } pub fn join(&self, entry: &RepoPathComponent) -> RepoPathBuf { let value = if self.value.is_empty() { entry.as_internal_str().to_owned() } else { [&self.value, "/", entry.as_internal_str()].concat() }; RepoPathBuf { value } }} impl AsRef<RepoPath> for RepoPath { fn as_ref(&self) -> &RepoPath { self }} impl AsRef<RepoPath> for RepoPathBuf { fn as_ref(&self) -> &RepoPath { self }} impl Borrow<RepoPath> for RepoPathBuf { fn borrow(&self) -> &RepoPath { self }} impl Deref for RepoPathBuf { type Target = RepoPath; fn deref(&self) -> &Self::Target { RepoPath::from_internal_string_unchecked(&self.value) }} impl ToOwned for RepoPath { type Owned = RepoPathBuf; fn to_owned(&self) -> Self::Owned { let value = self.value.to_owned(); RepoPathBuf { value } } fn clone_into(&self, target: &mut Self::Owned) { self.value.clone_into(&mut target.value); }} impl Ord for RepoPath { fn cmp(&self, other: &Self) -> Ordering { // If there were leading/trailing slash, components-based Ord would // disagree with str-based Eq. debug_assert!(is_valid_repo_path_str(&self.value)); self.components().cmp(other.components()) }} impl Ord for RepoPathBuf { fn cmp(&self, other: &Self) -> Ordering { <RepoPath as Ord>::cmp(self, other) }} impl PartialOrd for RepoPath {Lines 1–500 of 1187. Load more
// Copyright 2020 The Jujutsu Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #![allow(missing_docs)] use std::borrow::Borrow; use std::cmp::Ordering; use std::fmt; use std::fmt::Debug; use std::fmt::Formatter; use std::iter::FusedIterator; use std::ops::Deref; use std::path::Component; use std::path::Path; use std::path::PathBuf; use itertools::Itertools as _; use ref_cast::ref_cast_custom; use ref_cast::RefCastCustom; use thiserror::Error; use crate::content_hash::ContentHash; use crate::file_util; /// Owned `RepoPath` component. #[derive(ContentHash, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct RepoPathComponentBuf { // Don't add more fields. Eq, Hash, and Ord must be compatible with the // borrowed RepoPathComponent type. value: String, } impl RepoPathComponentBuf { /// Wraps `value` as `RepoPathComponentBuf`. /// /// Returns an error if the input `value` is empty or contains path /// separator. pub fn new(value: impl Into<String>) -> Result<Self, InvalidNewRepoPathError> { let value: String = value.into(); if is_valid_repo_path_component_str(&value) { Ok(Self { value }) } else { Err(InvalidNewRepoPathError { value }) } } } /// Borrowed `RepoPath` component. #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, RefCastCustom)] #[repr(transparent)] pub struct RepoPathComponent { value: str, } impl RepoPathComponent { /// Wraps `value` as `RepoPathComponent`. /// /// Returns an error if the input `value` is empty or contains path /// separator. pub fn new(value: &str) -> Result<&Self, InvalidNewRepoPathError> { if is_valid_repo_path_component_str(value) { Ok(Self::new_unchecked(value)) } else { Err(InvalidNewRepoPathError { value: value.to_string(), }) } } #[ref_cast_custom] const fn new_unchecked(value: &str) -> &Self; /// Returns the underlying string representation. pub fn as_internal_str(&self) -> &str { &self.value } /// Returns a normal filesystem entry name if this path component is valid /// as a file/directory name. pub fn to_fs_name(&self) -> Result<&str, InvalidRepoPathComponentError> { let mut components = Path::new(&self.value).components().fuse(); match (components.next(), components.next()) { // Trailing "." can be normalized by Path::components(), so compare // component name. e.g. "foo\." (on Windows) should be rejected. (Some(Component::Normal(name)), None) if name == &self.value => Ok(&self.value), // e.g. ".", "..", "foo\bar" (on Windows) _ => Err(InvalidRepoPathComponentError { component: self.value.into(), }), } } } impl Debug for RepoPathComponent { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{:?}", &self.value) } } impl Debug for RepoPathComponentBuf { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { <RepoPathComponent as Debug>::fmt(self, f) } } impl AsRef<RepoPathComponent> for RepoPathComponent { fn as_ref(&self) -> &RepoPathComponent { self } } impl AsRef<RepoPathComponent> for RepoPathComponentBuf { fn as_ref(&self) -> &RepoPathComponent { self } } impl Borrow<RepoPathComponent> for RepoPathComponentBuf { fn borrow(&self) -> &RepoPathComponent { self } } impl Deref for RepoPathComponentBuf { type Target = RepoPathComponent; fn deref(&self) -> &Self::Target { RepoPathComponent::new_unchecked(&self.value) } } impl ToOwned for RepoPathComponent { type Owned = RepoPathComponentBuf; fn to_owned(&self) -> Self::Owned { let value = self.value.to_owned(); RepoPathComponentBuf { value } } fn clone_into(&self, target: &mut Self::Owned) { self.value.clone_into(&mut target.value); } } /// Iterator over `RepoPath` components. #[derive(Clone, Debug)] pub struct RepoPathComponentsIter<'a> { value: &'a str, } impl<'a> RepoPathComponentsIter<'a> { /// Returns the remaining part as repository path. pub fn as_path(&self) -> &'a RepoPath { RepoPath::from_internal_string_unchecked(self.value) } } impl<'a> Iterator for RepoPathComponentsIter<'a> { type Item = &'a RepoPathComponent; fn next(&mut self) -> Option<Self::Item> { if self.value.is_empty() { return None; } let (name, remainder) = self .value .split_once('/') .unwrap_or_else(|| (self.value, &self.value[self.value.len()..])); self.value = remainder; Some(RepoPathComponent::new_unchecked(name)) } } impl DoubleEndedIterator for RepoPathComponentsIter<'_> { fn next_back(&mut self) -> Option<Self::Item> { if self.value.is_empty() { return None; } let (remainder, name) = self .value .rsplit_once('/') .unwrap_or_else(|| (&self.value[..0], self.value)); self.value = remainder; Some(RepoPathComponent::new_unchecked(name)) } } impl FusedIterator for RepoPathComponentsIter<'_> {} /// Owned repository path. #[derive(ContentHash, Clone, Eq, Hash, PartialEq, serde::Serialize)] #[serde(transparent)] pub struct RepoPathBuf { // Don't add more fields. Eq, Hash, and Ord must be compatible with the // borrowed RepoPath type. value: String, } /// Borrowed repository path. #[derive(ContentHash, Eq, Hash, PartialEq, RefCastCustom, serde::Serialize)] #[repr(transparent)] #[serde(transparent)] pub struct RepoPath { value: str, } impl Debug for RepoPath { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{:?}", &self.value) } } impl Debug for RepoPathBuf { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { <RepoPath as Debug>::fmt(self, f) } } /// The `value` is not a valid repo path because it contains empty path /// component. For example, `"/"`, `"/foo"`, `"foo/"`, `"foo//bar"` are all /// invalid. #[derive(Clone, Debug, Eq, Error, PartialEq)] #[error(r#"Invalid repo path input "{value}""#)] pub struct InvalidNewRepoPathError { value: String, } impl RepoPathBuf { /// Creates owned repository path pointing to the root. pub const fn root() -> Self { RepoPathBuf { value: String::new(), } } /// Creates `RepoPathBuf` from valid string representation. pub fn from_internal_string(value: impl Into<String>) -> Result<Self, InvalidNewRepoPathError> { let value: String = value.into(); if is_valid_repo_path_str(&value) { Ok(RepoPathBuf { value }) } else { Err(InvalidNewRepoPathError { value }) } } /// Converts repo-relative `Path` to `RepoPathBuf`. /// /// The input path should not contain redundant `.` or `..`. pub fn from_relative_path( relative_path: impl AsRef<Path>, ) -> Result<Self, RelativePathParseError> { let relative_path = relative_path.as_ref(); if relative_path == Path::new(".") { return Ok(Self::root()); } let mut components = relative_path .components() .map(|c| match c { Component::Normal(name) => { name.to_str() .ok_or_else(|| RelativePathParseError::InvalidUtf8 { path: relative_path.into(), }) } _ => Err(RelativePathParseError::InvalidComponent { component: c.as_os_str().to_string_lossy().into(), path: relative_path.into(), }), }) .fuse(); let mut value = String::with_capacity(relative_path.as_os_str().len()); if let Some(name) = components.next() { value.push_str(name?); } for name in components { value.push('/'); value.push_str(name?); } Ok(RepoPathBuf { value }) } /// Parses an `input` path into a `RepoPathBuf` relative to `base`. /// /// The `cwd` and `base` paths are supposed to be absolute and normalized in /// the same manner. The `input` path may be either relative to `cwd` or /// absolute. pub fn parse_fs_path( cwd: &Path, base: &Path, input: impl AsRef<Path>, ) -> Result<Self, FsPathParseError> { let input = input.as_ref(); let abs_input_path = file_util::normalize_path(&cwd.join(input)); let repo_relative_path = file_util::relative_path(base, &abs_input_path); Self::from_relative_path(repo_relative_path).map_err(|source| FsPathParseError { base: file_util::relative_path(cwd, base).into(), input: input.into(), source, }) } /// Consumes this and returns the underlying string representation. pub fn into_internal_string(self) -> String { self.value } } impl RepoPath { /// Returns repository path pointing to the root. pub const fn root() -> &'static Self { Self::from_internal_string_unchecked("") } /// Wraps valid string representation as `RepoPath`. /// /// Returns an error if the input `value` contains empty path component. For /// example, `"/"`, `"/foo"`, `"foo/"`, `"foo//bar"` are all invalid. pub fn from_internal_string(value: &str) -> Result<&Self, InvalidNewRepoPathError> { if is_valid_repo_path_str(value) { Ok(Self::from_internal_string_unchecked(value)) } else { Err(InvalidNewRepoPathError { value: value.to_owned(), }) } } #[ref_cast_custom] const fn from_internal_string_unchecked(value: &str) -> &Self; /// The full string form used internally, not for presenting to users (where /// we may want to use the platform's separator). This format includes a /// trailing slash, unless this path represents the root directory. That /// way it can be concatenated with a basename and produce a valid path. pub fn to_internal_dir_string(&self) -> String { if self.value.is_empty() { String::new() } else { [&self.value, "/"].concat() } } /// The full string form used internally, not for presenting to users (where /// we may want to use the platform's separator). pub fn as_internal_file_string(&self) -> &str { &self.value } /// Converts repository path to filesystem path relative to the `base`. /// /// The returned path should never contain `..`, `C:` (on Windows), etc. /// However, it may contain reserved working-copy directories such as `.jj`. pub fn to_fs_path(&self, base: &Path) -> Result<PathBuf, InvalidRepoPathError> { let mut result = PathBuf::with_capacity(base.as_os_str().len() + self.value.len() + 1); result.push(base); for c in self.components() { result.push(c.to_fs_name().map_err(|err| err.with_path(self))?); } if result.as_os_str().is_empty() { result.push("."); } Ok(result) } /// Converts repository path to filesystem path relative to the `base`, /// without checking invalid path components. /// /// The returned path may point outside of the `base` directory. Use this /// function only for displaying or testing purposes. pub fn to_fs_path_unchecked(&self, base: &Path) -> PathBuf { let mut result = PathBuf::with_capacity(base.as_os_str().len() + self.value.len() + 1); result.push(base); result.extend(self.components().map(RepoPathComponent::as_internal_str)); if result.as_os_str().is_empty() { result.push("."); } result } pub fn is_root(&self) -> bool { self.value.is_empty() } /// Returns true if the `base` is a prefix of this path. pub fn starts_with(&self, base: &RepoPath) -> bool { self.strip_prefix(base).is_some() } /// Returns the remaining path with the `base` path removed. pub fn strip_prefix(&self, base: &RepoPath) -> Option<&RepoPath> { if base.value.is_empty() { Some(self) } else { let tail = self.value.strip_prefix(&base.value)?; if tail.is_empty() { Some(RepoPath::from_internal_string_unchecked(tail)) } else { tail.strip_prefix('/') .map(RepoPath::from_internal_string_unchecked) } } } /// Returns the parent path without the base name component. pub fn parent(&self) -> Option<&RepoPath> { self.split().map(|(parent, _)| parent) } /// Splits this into the parent path and base name component. pub fn split(&self) -> Option<(&RepoPath, &RepoPathComponent)> { let mut components = self.components(); let basename = components.next_back()?; Some((components.as_path(), basename)) } pub fn components(&self) -> RepoPathComponentsIter<'_> { RepoPathComponentsIter { value: &self.value } } pub fn ancestors(&self) -> impl Iterator<Item = &RepoPath> { std::iter::successors(Some(self), |path| path.parent()) } pub fn join(&self, entry: &RepoPathComponent) -> RepoPathBuf { let value = if self.value.is_empty() { entry.as_internal_str().to_owned() } else { [&self.value, "/", entry.as_internal_str()].concat() }; RepoPathBuf { value } } } impl AsRef<RepoPath> for RepoPath { fn as_ref(&self) -> &RepoPath { self } } impl AsRef<RepoPath> for RepoPathBuf { fn as_ref(&self) -> &RepoPath { self } } impl Borrow<RepoPath> for RepoPathBuf { fn borrow(&self) -> &RepoPath { self } } impl Deref for RepoPathBuf { type Target = RepoPath; fn deref(&self) -> &Self::Target { RepoPath::from_internal_string_unchecked(&self.value) } } impl ToOwned for RepoPath { type Owned = RepoPathBuf; fn to_owned(&self) -> Self::Owned { let value = self.value.to_owned(); RepoPathBuf { value } } fn clone_into(&self, target: &mut Self::Owned) { self.value.clone_into(&mut target.value); } } impl Ord for RepoPath { fn cmp(&self, other: &Self) -> Ordering { // If there were leading/trailing slash, components-based Ord would // disagree with str-based Eq. debug_assert!(is_valid_repo_path_str(&self.value)); self.components().cmp(other.components()) } } impl Ord for RepoPathBuf { fn cmp(&self, other: &Self) -> Ordering { <RepoPath as Ord>::cmp(self, other) } } impl PartialOrd for RepoPath {