1218 lines · 40.0 KB · Rust
RawDownload
// 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. #![expect(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::RefCastCustom;use ref_cast::ref_cast_custom;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<Self> for RepoPathComponent {    fn as_ref(&self) -> &Self {        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 {        Self {            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(Self { 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(Self { 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: &Self) -> bool {        self.strip_prefix(base).is_some()    }     /// Returns the remaining path with the `base` path removed.    pub fn strip_prefix(&self, base: &Self) -> Option<&Self> {        if base.value.is_empty() {            Some(self)        } else {            let tail = self.value.strip_prefix(&base.value)?;            if tail.is_empty() {                Some(Self::from_internal_string_unchecked(tail))            } else {                tail.strip_prefix('/')                    .map(Self::from_internal_string_unchecked)            }        }    }     /// Returns the parent path without the base name component.    pub fn parent(&self) -> Option<&Self> {        self.split().map(|(parent, _)| parent)    }     /// Splits this into the parent path and base name component.    pub fn split(&self) -> Option<(&Self, &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 = &Self> {        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<Self> for RepoPath {    fn as_ref(&self) -> &Self {        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 1218.