630 lines · 28.2 KB · Rust
RawDownload
use std::sync::atomic::AtomicBool; use crate::{    Repository,    bstr::{BStr, BString},    config,    config::cache::util::ApplyLeniencyDefault,};use gix_status::index_as_worktree::traits::{CompareBlobs, SubmoduleStatus}; /// The error returned by [Repository::index_worktree_status()].#[derive(Debug, thiserror::Error)]#[expect(missing_docs)]pub enum Error {    #[error("A working tree is required to perform a directory walk")]    MissingWorkDir,    #[error(transparent)]    AttributesAndExcludes(#[from] crate::repository::attributes::Error),    #[error(transparent)]    Pathspec(#[from] crate::pathspec::init::Error),    #[error(transparent)]    Prefix(#[from] gix_path::realpath::Error),    #[error(transparent)]    FilesystemOptions(#[from] config::boolean::Error),    #[error(transparent)]    IndexAsWorktreeWithRenames(#[from] gix_status::index_as_worktree_with_renames::Error),    #[error(transparent)]    StatOptions(#[from] config::stat_options::Error),    #[error(transparent)]    ResourceCache(#[from] crate::diff::resource_cache::Error),} /// Options for use with [Repository::index_worktree_status()].#[derive(Default, Debug, Clone, Copy, PartialEq)]pub struct Options {    /// The way all output should be sorted.    ///    /// If `None`, and depending on the `rewrites` field, output will be immediate but the output order    /// isn't determined, and may differ between two runs. `rewrites` also depend on the order of entries that    /// are presented to it, hence for deterministic results, sorting needs to be enabled.    ///    /// If `Some(_)`, all entries are collected beforehand, so they can be sorted before outputting any of them    /// to the user.    ///    /// If immediate output of entries in any order is desired, this should be `None`,    /// along with `rewrites` being `None` as well.    pub sorting: Option<gix_status::index_as_worktree_with_renames::Sorting>,    /// If not `None`, the options to configure the directory walk, determining how its results will look like.    ///    /// If `None`, only modification checks are performed.    ///    /// Can be instantiated with [Repository::dirwalk_options()].    pub dirwalk_options: Option<crate::dirwalk::Options>,    /// If `Some(_)`, along with `Some(_)` in `dirwalk_options`, rewrite tracking will be performed between the    /// index and the working tree.    /// Note that there is no git-configuration specific to index-worktree rename tracking.    /// When rewrite tracking is enabled, there will be a delay for some entries as they partake in the rename-analysis.    pub rewrites: Option<gix_diff::Rewrites>,    /// If set, don't use more than this amount of threads for the tracked modification check.    /// Otherwise, usually use as many threads as there are logical cores.    /// A value of 0 is interpreted as no-limit    pub thread_limit: Option<usize>,} impl Repository {    /// Obtain the status between the index and the worktree, involving modification checks    /// for all tracked files along with information about untracked (and posisbly ignored) files (if configured).    ///    /// * `index`    ///     - The index to use for modification checks, and to know which files are tacked when applying the dirwalk.    /// * `patterns`    ///     - Optional patterns to use to limit the paths to look at. If empty, all paths are considered.    /// * `delegate`    ///     - The sink for receiving all status data.    /// * `compare`    ///     - The implementations for fine-grained control over what happens if a hash must be recalculated.    /// * `submodule`    ///      - Control what kind of information to retrieve when a submodule is encountered while traversing the index.    /// * `progress`    ///     - A progress indication for index modification checks.    /// * `should_interrupt`    ///     - A flag to stop the whole operation.    /// * `options`    ///     - Additional configuration for all parts of the operation.    ///    /// ### Note    ///    /// This is a lower-level method, prefer the [`status`](Repository::status()) method for greater ease of use.    #[expect(clippy::too_many_arguments)]    pub fn index_worktree_status<'index, T, U, E>(        &self,        index: &'index gix_index::State,        patterns: impl IntoIterator<Item = impl AsRef<BStr>>,        delegate: &mut impl gix_status::index_as_worktree_with_renames::VisitEntry<            'index,            ContentChange = T,            SubmoduleStatus = U,        >,        compare: impl CompareBlobs<Output = T> + Send + Clone,        submodule: impl SubmoduleStatus<Output = U, Error = E> + Send + Clone,        progress: &mut dyn gix_features::progress::Progress,        should_interrupt: &AtomicBool,        options: Options,    ) -> Result<gix_status::index_as_worktree_with_renames::Outcome, Error>    where        T: Send + Clone,        U: Send + Clone,        E: std::error::Error + Send + Sync + 'static,    {        let _span = gix_trace::coarse!("gix::index_worktree_status");        let workdir = self.workdir().ok_or(Error::MissingWorkDir)?;        let attrs_and_excludes = self.attributes(            index,            crate::worktree::stack::state::attributes::Source::WorktreeThenIdMapping,            crate::worktree::stack::state::ignore::Source::WorktreeThenIdMappingIfNotSkipped,            None,        )?;        let pathspec =            self.index_worktree_status_pathspec::<Error>(patterns, index, options.dirwalk_options.as_ref())?;         let cwd = self.current_dir();        let git_dir_realpath = crate::path::realpath_opts(self.git_dir(), cwd, crate::path::realpath::MAX_SYMLINKS)?;        let fs_caps = self.filesystem_options()?;        let fscache = config::tree::Core::FS_CACHE            .enrich_error(self.config.resolved.boolean(config::tree::Core::FS_CACHE))            .with_lenient_default(self.config.lenient_config)?            // if unset, default to enabled on Windows. Good for missing Git installations that would turn it on by installation config            .unwrap_or(cfg!(windows));        let accelerate_lookup = fs_caps.ignore_case.then(|| index.prepare_icase_backing());        let resource_cache = crate::diff::resource_cache(            self,            gix_diff::blob::pipeline::Mode::ToGit,            attrs_and_excludes.inner,            gix_diff::blob::pipeline::WorktreeRoots {                old_root: None,                new_root: Some(workdir.to_owned()),            },        )?;         let out = gix_status::index_as_worktree_with_renames(            index,            workdir,            delegate,            compare,            submodule,            self.objects.clone().into_arc().expect("arc conversion always works"),            progress,            gix_status::index_as_worktree_with_renames::Context {                pathspec: pathspec.search,                resource_cache,                should_interrupt,                dirwalk: gix_status::index_as_worktree_with_renames::DirwalkContext {                    git_dir_realpath: git_dir_realpath.as_path(),                    current_dir: cwd,                    ignore_case_index_lookup: accelerate_lookup.as_ref(),                },            },            gix_status::index_as_worktree_with_renames::Options {                sorting: options.sorting,                object_hash: self.object_hash(),                tracked_file_modifications: gix_status::index_as_worktree::Options {                    fs: fs_caps,                    thread_limit: options.thread_limit,                    stat: self.stat_options()?,                    fscache,                },                fscache,                dirwalk: options.dirwalk_options.map(Into::into),                rewrites: options.rewrites,            },        )?;        Ok(out)    }     pub(super) fn index_worktree_status_pathspec<E>(        &self,        patterns: impl IntoIterator<Item = impl AsRef<BStr>>,        index: &gix_index::State,        options: Option<&crate::dirwalk::Options>,    ) -> Result<crate::Pathspec<'_>, E>    where        E: From<crate::repository::attributes::Error> + From<crate::pathspec::init::Error>,    {        let empty_patterns_match_prefix = options.is_some_and(|opts| opts.empty_patterns_match_prefix);        let attrs_and_excludes = self.attributes(            index,            crate::worktree::stack::state::attributes::Source::WorktreeThenIdMapping,            crate::worktree::stack::state::ignore::Source::WorktreeThenIdMappingIfNotSkipped,            None,        )?;        Ok(crate::Pathspec::new(            self,            empty_patterns_match_prefix,            patterns,            true, /* inherit ignore case */            move || Ok(attrs_and_excludes.inner),        )?)    }} /// An implementation of a trait to use with [`Repository::index_worktree_status()`] to compute the submodule status/// using [Submodule::status()](crate::Submodule::status()).#[derive(Clone)]pub struct BuiltinSubmoduleStatus {    mode: crate::status::Submodule,    #[cfg(feature = "parallel")]    repo: crate::ThreadSafeRepository,    #[cfg(not(feature = "parallel"))]    git_dir: std::path::PathBuf,    submodule_paths: Vec<BString>,} ///mod submodule_status {    use crate::config::cache::util::ApplyLeniency;    use crate::{        bstr,        bstr::BStr,        config,        status::{Submodule, index_worktree::BuiltinSubmoduleStatus},    };     impl BuiltinSubmoduleStatus {        /// Create a new instance from a `repo` and a `mode` to control how the submodule status will be obtained.        pub fn new(            repo: crate::ThreadSafeRepository,            mode: Submodule,        ) -> Result<Self, crate::submodule::modules::Error> {            let local_repo = repo.to_thread_local();            let submodule_paths = match local_repo.submodules() {                Ok(Some(sm)) => {                    let mut v: Vec<_> = sm.filter_map(|sm| sm.path().ok()).collect();                    v.sort();                    v                }                Ok(None) => Vec::new(),                Err(err) => return Err(err),            };            Ok(Self {                mode,                #[cfg(feature = "parallel")]                repo,                #[cfg(not(feature = "parallel"))]                git_dir: local_repo.git_dir().to_owned(),                submodule_paths,            })        }    }     /// The error returned submodule status checks.    #[derive(Debug, thiserror::Error)]    pub enum Error {        #[error(transparent)]        SubmoduleStatus(#[from] crate::submodule::status::Error),        #[error(transparent)]        IgnoreConfig(#[from] crate::submodule::config::Error),        #[error(transparent)]        DiffSubmoduleIgnoreConfig(#[from] config::key::GenericErrorWithValue),    }     impl gix_status::index_as_worktree::traits::SubmoduleStatus for BuiltinSubmoduleStatus {        type Output = crate::submodule::Status;        type Error = Error;         fn status(&mut self, _entry: &gix_index::Entry, rela_path: &BStr) -> Result<Option<Self::Output>, Self::Error> {            use bstr::ByteSlice;            if self                .submodule_paths                .binary_search_by(|path| path.as_bstr().cmp(rela_path))                .is_err()            {                return Ok(None);            }            #[cfg(feature = "parallel")]            let repo = self.repo.to_thread_local();            #[cfg(not(feature = "parallel"))]            let Ok(repo) = crate::open(&self.git_dir) else {                return Ok(None);            };            let Ok(Some(mut submodules)) = repo.submodules() else {                return Ok(None);            };            let Some(sm) = submodules.find(|sm| sm.path().is_ok_and(|path| path == rela_path)) else {                return Ok(None);            };            let (ignore, check_dirty) = match self.mode {                Submodule::AsConfigured { check_dirty } => {                    // diff.ignoreSubmodules is the global setting, and if it exists, it overrides the submodule's own ignore setting.                    let global_ignore = repo                        .config_snapshot()                        .string(config::tree::Diff::IGNORE_SUBMODULES)                        .map(|value| config::tree::Diff::IGNORE_SUBMODULES.try_into_ignore(value))                        .transpose()                        .with_leniency(repo.config.lenient_config)?;                    if let Some(ignore) = global_ignore {                        (ignore, check_dirty)                    } else {                        // If no global ignore is set, use the submodule's ignore setting.                        let ignore = sm.ignore()?.unwrap_or_default();                        (ignore, check_dirty)                    }                }                Submodule::Given { ignore, check_dirty } => (ignore, check_dirty),            };            let status = sm.status(ignore, check_dirty)?;            Ok(status.is_dirty().and_then(|dirty| dirty.then_some(status)))        }    }} /// An iterator for changes between the index and the worktree.////// Note that depending on the underlying configuration, there might be a significant delay until the first/// item is received due to the buffering necessary to perform rename tracking and/or sorting.////// ### Submodules////// Note that submodules can be set to 'inactive', which will not exclude them from the status operation, similar to/// how `git status` includes them.////// ### Index Changes////// Changes to the index are collected and it's possible to write the index back using [Outcome::write_changes()](crate::status::Outcome)./// Note that these changes are not observable, they will always be kept.////// ### Parallel Operation////// Note that without the `parallel` feature, the iterator becomes 'serial', which means all status will be computed in advance/// and it's non-interruptible, yielding worse performance for is-dirty checks for instance as interruptions won't happen./// It's a crutch that is just there to make single-threaded applications possible at all, as it's not really an iterator/// anymore. If this matters, better run [Repository::index_worktree_status()] by hand as it provides all control one would need,/// just not as an iterator.////// Also, even with `parallel` set, the first call to `next()` will block until there is an item available, without a chance/// to interrupt unless [`status::Platform::should_interrupt_*()`](crate::status::Platform::should_interrupt_shared()) was/// configured.pub struct Iter {    inner: crate::status::Iter,} /// The item produced by the iterator#[derive(Clone, PartialEq, Debug)]pub enum Item {    /// A tracked file was modified, and index-specific information is passed.    Modification {        /// The entry with modifications.        entry: gix_index::Entry,        /// The index of the `entry` for lookup in [`gix_index::State::entries()`] - useful to look at neighbors.        entry_index: usize,        /// The repository-relative path of the entry.        rela_path: BString,        /// The computed status of the entry.        status: gix_status::index_as_worktree::EntryStatus<(), crate::submodule::Status>,    },    /// An entry returned by the directory walk, without any relation to the index.    ///    /// This can happen if ignored files are returned as well, or if rename-tracking is disabled.    DirectoryContents {        /// The entry found during the disk traversal.        entry: gix_dir::Entry,        /// `collapsed_directory_status` is `Some(dir_status)` if this `entry` was part of a directory with the given        /// `dir_status` that wasn't the same as the one of `entry` and if [gix_dir::walk::Options::emit_collapsed] was        /// [CollapsedEntriesEmissionMode::OnStatusMismatch](gix_dir::walk::CollapsedEntriesEmissionMode::OnStatusMismatch).        /// It will also be `Some(dir_status)` if that option was [CollapsedEntriesEmissionMode::All](gix_dir::walk::CollapsedEntriesEmissionMode::All).        collapsed_directory_status: Option<gix_dir::entry::Status>,    },    /// The rewrite tracking discovered a match between a deleted and added file, and considers them equal enough,    /// depending on the tracker settings.    ///    /// Note that the source of the rewrite is always the index as it detects the absence of entries, something that    /// can't be done during a directory walk.    Rewrite {        /// The source of the rewrite operation.        source: RewriteSource,        /// The untracked entry found during the disk traversal, the destination of the rewrite.        ///        /// Note that its [`rela_path`](gix_dir::EntryRef::rela_path) is the destination of the rewrite, and the current        /// location of the entry.        dirwalk_entry: gix_dir::Entry,        /// `collapsed_directory_status` is `Some(dir_status)` if this `dirwalk_entry` was part of a directory with the given        /// `dir_status` that wasn't the same as the one of `dirwalk_entry` and if [gix_dir::walk::Options::emit_collapsed] was        /// [CollapsedEntriesEmissionMode::OnStatusMismatch](gix_dir::walk::CollapsedEntriesEmissionMode::OnStatusMismatch).        /// It will also be `Some(dir_status)` if that option was [CollapsedEntriesEmissionMode::All](gix_dir::walk::CollapsedEntriesEmissionMode::All).        dirwalk_entry_collapsed_directory_status: Option<gix_dir::entry::Status>,        /// The object id as it would appear if the entry was written to the object database, specifically hashed in order to determine equality.        /// Note that it doesn't (necessarily) exist in the object database, and may be [null](gix_hash::ObjectId::null) if no hashing        /// was performed.        dirwalk_entry_id: gix_hash::ObjectId,        /// It's `None` if the 'source.id' is equal to `dirwalk_entry_id`, as identity made an actual diff computation unnecessary.        /// Otherwise, and if enabled, it's `Some(stats)` to indicate how similar both entries were.        diff: Option<gix_diff::blob::DiffLineStats>,        /// If true, this rewrite is created by copy, and 'source.id' is pointing to its source.        /// Otherwise, it's a rename, and 'source.id' points to a deleted object,        /// as renames are tracked as deletions and additions of the same or similar content.        copy: bool,    },} /// Either an index entry for renames or another directory entry in case of copies.#[derive(Clone, PartialEq, Debug)]pub enum RewriteSource {    /// The source originates in the index and is detected as missing in the working tree.    /// This can also happen for copies.    RewriteFromIndex {        /// The entry that is the source of the rewrite, which means it was removed on disk,        /// equivalent to [Change::Removed](gix_status::index_as_worktree::Change::Removed).        ///        /// Note that the [entry-id](gix_index::Entry::id) is the content-id of the source of the rewrite.        source_entry: gix_index::Entry,        /// The index of the `source_entry` for lookup in [`gix_index::State::entries()`] - useful to look at neighbors.        source_entry_index: usize,        /// The repository-relative path of the `source_entry`.        source_rela_path: BString,        /// The computed status of the `source_entry`.        source_status: gix_status::index_as_worktree::EntryStatus<(), crate::submodule::Status>,    },    /// This source originates in the directory tree and is always the source of copies.    CopyFromDirectoryEntry {        /// The source of the copy operation, which is also an entry of the directory walk.        ///        /// Note that its [`rela_path`](gix_dir::EntryRef::rela_path) is the source of the rewrite.        source_dirwalk_entry: gix_dir::Entry,        /// `collapsed_directory_status` is `Some(dir_status)` if this `source_dirwalk_entry` was part of a directory with the given        /// `dir_status` that wasn't the same as the one of `source_dirwalk_entry` and        /// if [gix_dir::walk::Options::emit_collapsed] was [CollapsedEntriesEmissionMode::OnStatusMismatch](gix_dir::walk::CollapsedEntriesEmissionMode::OnStatusMismatch).        /// It will also be `Some(dir_status)` if that option was [CollapsedEntriesEmissionMode::All](gix_dir::walk::CollapsedEntriesEmissionMode::All).        source_dirwalk_entry_collapsed_directory_status: Option<gix_dir::entry::Status>,        /// The object id as it would appear if the entry was written to the object database.        /// It's the same as [`dirwalk_entry_id`](Item::Rewrite), or `diff` is `Some(_)` to indicate that the copy        /// was determined by similarity, not by content equality.        source_dirwalk_entry_id: gix_hash::ObjectId,    },} ///pub mod iter {    use gix_status::index_as_worktree::{Change, EntryStatus};    pub use gix_status::index_as_worktree_with_renames::Summary;     use super::{Item, RewriteSource};    use crate::{        bstr::{BStr, BString},        status::{Platform, index_worktree},    };     /// Access    impl RewriteSource {        /// The repository-relative path of this source.        pub fn rela_path(&self) -> &BStr {            match self {                RewriteSource::RewriteFromIndex { source_rela_path, .. } => source_rela_path.as_ref(),                RewriteSource::CopyFromDirectoryEntry {                    source_dirwalk_entry, ..                } => source_dirwalk_entry.rela_path.as_ref(),            }        }    }     impl<'index> From<gix_status::index_as_worktree_with_renames::RewriteSource<'index, (), SubmoduleStatus>>        for RewriteSource    {        fn from(value: gix_status::index_as_worktree_with_renames::RewriteSource<'index, (), SubmoduleStatus>) -> Self {            match value {                gix_status::index_as_worktree_with_renames::RewriteSource::RewriteFromIndex {                    index_entries: _,                    source_entry,                    source_entry_index,                    source_rela_path,                    source_status,                } => RewriteSource::RewriteFromIndex {                    source_entry: source_entry.clone(),                    source_entry_index,                    source_rela_path: source_rela_path.to_owned(),                    source_status,                },                gix_status::index_as_worktree_with_renames::RewriteSource::CopyFromDirectoryEntry {                    source_dirwalk_entry,                    source_dirwalk_entry_collapsed_directory_status,                    source_dirwalk_entry_id,                } => RewriteSource::CopyFromDirectoryEntry {                    source_dirwalk_entry,                    source_dirwalk_entry_collapsed_directory_status,                    source_dirwalk_entry_id,                },            }        }    }     impl Item {        /// Return a simplified summary of the item as digest of its status, or `None` if this item is        /// created from the directory walk and is *not untracked*, or if it is merely to communicate        /// a needed update to the index entry.        pub fn summary(&self) -> Option<Summary> {            use gix_status::index_as_worktree_with_renames::Summary::*;            Some(match self {                Item::Modification { status, .. } => match status {                    EntryStatus::Conflict { .. } => Conflict,                    EntryStatus::Change(change) => match change {                        Change::Removed => Removed,                        Change::Type { .. } => TypeChange,

Lines 1–500 of 630.