227 lines · 8.9 KB · Rust
RawDownload
use std::{borrow::Cow, fs::FileType}; use crate::{Entry, EntryRef, walk::ForDeletionMode}; /// A way of attaching additional information to an [Entry] .#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]pub enum Property {    /// The entry was named `.git`, matched according to the case-sensitivity rules of the repository.    DotGit,    /// The entry is a directory, and that directory is empty.    EmptyDirectory,    /// The entry is a directory, it is empty and the current working directory.    ///    /// The caller should pay special attention to this very special case, as it is indeed only possible to run into it    /// while traversing the directory for deletion.    /// Non-empty directory will never be collapsed, hence if they are working directories, they naturally become unobservable.    EmptyDirectoryAndCWD,    /// Always in conjunction with a directory on disk that is also known as cone-mode sparse-checkout exclude marker    /// - i.e. a directory that is excluded, so its whole content is excluded and not checked out nor is part of the index.    ///    /// Note that evne if the directory is empty, it will only have this state, not `EmptyDirectory`.    TrackedExcluded,} /// The kind of the entry, seated in their kinds available on disk.#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]pub enum Kind {    /// Something that is not a regular file, directory, or symbolic link.    ///    /// These can only exist in the filesystem,    /// because Git repositories do not support them, thus they cannot be tracked.    /// Hence, they do not appear as blobs in a repository, and their type is not specifiable in a tree object.    /// Examples include named pipes (FIFOs), character devices, block devices, and sockets.    Untrackable,    /// The entry is a blob, representing a regular file, executable or not.    File,    /// The entry is a symlink.    Symlink,    /// The entry is an ordinary directory.    ///    /// Note that since we don't check for bare repositories, this could in fact be a collapsed    /// bare repository. To be sure, check it again with [`gix_discover::is_git()`] and act accordingly.    Directory,    /// The entry is a directory which *contains* a `.git` folder, or a submodule entry in the index.    Repository,} /// The kind of entry as obtained from a directory.#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]pub enum Status {    /// The entry was removed from the walk due to its other properties, like [Property] or [PathspecMatch]    ///    /// Note that entries flagged as `DotGit` directory will always be considered `Pruned`, but if they are    /// also ignored, in delete mode, they will be considered `Ignored` instead. This way, it's easier to remove them    /// while they will not be available for any interactions in read-only mode.    Pruned,    /// The entry is tracked in Git.    Tracked,    /// The entry is ignored as per `.gitignore` files and their rules.    ///    /// If this is a directory, then its entire contents is ignored. Otherwise, possibly due to configuration, individual ignored files are listed.    Ignored(gix_ignore::Kind),    /// The entry is not tracked by git yet, it was not found in the [index](gix_index::State).    ///    /// If it's a directory, the entire directory contents is untracked.    Untracked,} /// Describe how a pathspec pattern matched.#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)]pub enum PathspecMatch {    /// The match happened because there wasn't any pattern, which matches all, or because there was a nil pattern or one with an empty path.    /// Thus, this is not a match by merit.    Always,    /// A match happened, but the pattern excludes everything it matches, which means this entry was excluded.    Excluded,    /// The first part of a pathspec matches, like `dir/` that matches `dir/a`.    Prefix,    /// The whole pathspec matched and used a wildcard match, like `a/*` matching `a/file`.    WildcardMatch,    /// The entire pathspec matched, letter by letter, e.g. `a/file` matching `a/file`.    Verbatim,} impl PathspecMatch {    pub(crate) fn should_ignore(&self) -> bool {        match self {            PathspecMatch::Always | PathspecMatch::Excluded => true,            PathspecMatch::Prefix | PathspecMatch::WildcardMatch | PathspecMatch::Verbatim => false,        }    }} impl From<gix_pathspec::search::MatchKind> for PathspecMatch {    fn from(kind: gix_pathspec::search::MatchKind) -> Self {        match kind {            gix_pathspec::search::MatchKind::Always => Self::Always,            gix_pathspec::search::MatchKind::Prefix => Self::Prefix,            gix_pathspec::search::MatchKind::WildcardMatch => Self::WildcardMatch,            gix_pathspec::search::MatchKind::Verbatim => Self::Verbatim,        }    }} impl From<gix_pathspec::search::Match<'_>> for PathspecMatch {    fn from(m: gix_pathspec::search::Match<'_>) -> Self {        if m.is_excluded() {            PathspecMatch::Excluded        } else {            m.kind.into()        }    }} /// Conversionimpl EntryRef<'_> {    /// Strip the lifetime to obtain a fully owned copy.    pub fn to_owned(&self) -> Entry {        Entry {            rela_path: self.rela_path.clone().into_owned(),            status: self.status,            property: self.property,            disk_kind: self.disk_kind,            index_kind: self.index_kind,            pathspec_match: self.pathspec_match,        }    }     /// Turn this instance into a fully owned copy.    pub fn into_owned(self) -> Entry {        Entry {            rela_path: self.rela_path.into_owned(),            status: self.status,            property: self.property,            disk_kind: self.disk_kind,            index_kind: self.index_kind,            pathspec_match: self.pathspec_match,        }    }} /// Conversionimpl Entry {    /// Obtain an [`EntryRef`] from this instance.    pub fn to_ref(&self) -> EntryRef<'_> {        EntryRef {            rela_path: Cow::Borrowed(self.rela_path.as_ref()),            status: self.status,            property: self.property,            disk_kind: self.disk_kind,            index_kind: self.index_kind,            pathspec_match: self.pathspec_match,        }    }} impl From<std::fs::FileType> for Kind {    fn from(value: FileType) -> Self {        if value.is_dir() {            Kind::Directory        } else if value.is_symlink() {            Kind::Symlink        } else if value.is_file() {            Kind::File        } else {            Kind::Untrackable        }    }} impl Status {    /// Return true if this status is considered pruned. A pruned entry is typically hidden from view due to a pathspec.    pub fn is_pruned(&self) -> bool {        matches!(&self, Status::Pruned)    }    /// Return `true` if `file_type` is a directory on disk and isn't ignored, and is not a repository.    /// This implements the default rules of `git status`, which is good for a minimal traversal through    /// tracked and non-ignored portions of a worktree.    /// `for_deletion` is used to determine if recursion into a directory is allowed even though it otherwise wouldn't be.    /// If `worktree_root_is_repository` is `true`, then this status is part of the root of an iteration, and the corresponding    /// worktree root is a repository itself. This typically happens for submodules. In this case, recursion rules are relaxed    /// to allow traversing submodule worktrees.    ///    /// Use `pathspec_match` to determine if a pathspec matches in any way, affecting the decision to recurse.    pub fn can_recurse(        &self,        file_type: Option<Kind>,        pathspec_match: Option<PathspecMatch>,        for_deletion: Option<ForDeletionMode>,        worktree_root_is_repository: bool,    ) -> bool {        let is_dir_on_disk = file_type.is_some_and(|ft| {            if worktree_root_is_repository {                ft.is_dir()            } else {                ft.is_recursable_dir()            }        });        if !is_dir_on_disk {            return false;        }        match self {            Status::Pruned => false,            Status::Ignored(_) => {                for_deletion.is_some_and(|fd| {                    matches!(                        fd,                        ForDeletionMode::FindNonBareRepositoriesInIgnoredDirectories                            | ForDeletionMode::FindRepositoriesInIgnoredDirectories                    )                }) || pathspec_match.is_some_and(|m| !m.should_ignore())            }            Status::Untracked | Status::Tracked => true,        }    }} impl Kind {    pub(super) fn is_recursable_dir(&self) -> bool {        matches!(self, Kind::Directory)    }     /// Return `true` if this is a directory on disk. Note that this is true for repositories as well.    pub fn is_dir(&self) -> bool {        matches!(self, Kind::Directory | Kind::Repository)    }}