353 lines · 14.3 KB · Rust
RawDownload
use std::sync::atomic::Ordering; use gix_status::index_as_worktree::{Change, EntryStatus}; use crate::{    bstr::BString,    config::cache::util::ApplyLeniencyDefault,    status::{Platform, index_worktree, index_worktree::BuiltinSubmoduleStatus, tree_index},    worktree::IndexPersistedOrInMemory,}; pub(super) mod types;use types::{ApplyChange, Item, Iter, Outcome}; /// Lifecycleimpl<Progress> Platform<'_, Progress>where    Progress: gix_features::progress::Progress,{    /// Turn the platform into an iterator for changes between the head-tree and the index, and the index and the working tree,    /// while optionally listing untracked and/or ignored files.    ///    /// * `patterns`    ///     - Optional patterns to use to limit the paths to look at. If empty, all paths are considered.    ///    /// ### Important: Undefined ordering    ///    /// When compiled with the `parallel` feature, three operations are run at once:    ///    /// * a dirwalk to find untracked and possibly ignored files    /// * an entry-by-entry check to see which of the tracked files changed, and how    /// * a tree-index comparison    ///    /// All of these generate distinct events which may now happen in any order, so consumers    /// that are ordering dependent have to restore their desired order.    ///    /// This isn't feasible to do here as it would mean that returned items would have to be delayed,    /// degrading performance for everyone who isn't order-dependent.    #[doc(alias = "diff_index_to_workdir", alias = "git2")]    pub fn into_iter(        self,        patterns: impl IntoIterator<Item = BString>,    ) -> Result<Iter, crate::status::into_iter::Error> {        let index = match self.index {            None => IndexPersistedOrInMemory::Persisted(self.repo.index_or_empty()?),            Some(index) => index,        };         let obtain_tree_id = || -> Result<Option<gix_hash::ObjectId>, crate::status::into_iter::Error> {            Ok(match self.head_tree {                Some(None) => Some(self.repo.head_tree_id_or_empty()?.into()),                Some(Some(tree_id)) => Some(tree_id),                None => None,            })        };         let skip_hash = crate::config::tree::Index::SKIP_HASH            .enrich_error(self.repo.config.resolved.boolean(crate::config::tree::Index::SKIP_HASH))            .with_lenient_default(self.repo.config.lenient_config)?            .unwrap_or_default();        let should_interrupt = self.should_interrupt.clone().unwrap_or_default();        let submodule = BuiltinSubmoduleStatus::new(self.repo.clone().into_sync(), self.submodules)?;        #[cfg(feature = "parallel")]        {            let (tx, rx) = std::sync::mpsc::channel();            let patterns: Vec<_> = patterns.into_iter().collect();            let join_tree_index = if let Some(tree_id) = obtain_tree_id()? {                std::thread::Builder::new()                    .name("gix::status::tree_index::producer".into())                    .spawn({                        let repo = self.repo.clone().into_sync();                        let should_interrupt = should_interrupt.clone();                        let tx = tx.clone();                        let tree_index_renames = self.tree_index_renames;                        let index = index.clone();                        let crate::Pathspec { repo: _, stack, search } = self                            .repo                            .index_worktree_status_pathspec::<crate::status::into_iter::Error>(                                &patterns,                                &index,                                self.index_worktree_options.dirwalk_options.as_ref(),                            )?;                        move || -> Result<_, _> {                            let repo = repo.to_thread_local();                            let mut pathspec = crate::Pathspec {                                repo: &repo,                                stack,                                search,                            };                            repo.tree_index_status(                                &tree_id,                                &index,                                Some(&mut pathspec),                                tree_index_renames,                                |change, _, _| {                                    let action = if tx.send(change.into_owned().into()).is_err()                                        || should_interrupt.load(Ordering::Acquire)                                    {                                        std::ops::ControlFlow::Break(())                                    } else {                                        std::ops::ControlFlow::Continue(())                                    };                                    Ok::<_, std::convert::Infallible>(action)                                },                            )                        }                    })                    .map_err(crate::status::into_iter::Error::SpawnThread)?                    .into()            } else {                None            };            let mut collect = Collect { tx };            let join_index_worktree = std::thread::Builder::new()                .name("gix::status::index_worktree::producer".into())                .spawn({                    let repo = self.repo.clone().into_sync();                    let options = self.index_worktree_options;                    let should_interrupt = should_interrupt.clone();                    let mut progress = self.progress;                    move || -> Result<_, index_worktree::Error> {                        let repo = repo.to_thread_local();                        let out = repo.index_worktree_status(                            &index,                            patterns,                            &mut collect,                            gix_status::index_as_worktree::traits::FastEq,                            submodule,                            &mut progress,                            &should_interrupt,                            options,                        )?;                        Ok(Outcome {                            index_worktree: out,                            tree_index: None,                            worktree_index: index,                            changes: None,                            skip_hash,                        })                    }                })                .map_err(crate::status::into_iter::Error::SpawnThread)?;             Ok(Iter {                rx_and_join: Some((rx, join_index_worktree, join_tree_index)),                should_interrupt,                index_changes: Vec::new(),                out: None,            })        }        #[cfg(not(feature = "parallel"))]        {            let mut collect = Collect { items: Vec::new() };             let repo = self.repo;            let options = self.index_worktree_options;            let mut progress = self.progress;            let patterns: Vec<BString> = patterns.into_iter().collect();            let (mut items, tree_index) = match obtain_tree_id()? {                Some(tree_id) => {                    let mut pathspec = repo.index_worktree_status_pathspec::<crate::status::into_iter::Error>(                        &patterns,                        &index,                        self.index_worktree_options.dirwalk_options.as_ref(),                    )?;                    let mut items = Vec::new();                    let tree_index = self.repo.tree_index_status(                        &tree_id,                        &index,                        Some(&mut pathspec),                        self.tree_index_renames,                        |change, _, _| {                            items.push(change.into_owned().into());                            let action = if should_interrupt.load(Ordering::Acquire) {                                std::ops::ControlFlow::Break(())                            } else {                                std::ops::ControlFlow::Continue(())                            };                            Ok::<_, std::convert::Infallible>(action)                        },                    )?;                    (items, Some(tree_index))                }                None => (Vec::new(), None),            };            let out = repo.index_worktree_status(                &index,                patterns,                &mut collect,                gix_status::index_as_worktree::traits::FastEq,                submodule,                &mut progress,                &should_interrupt,                options,            )?;            let mut iter = Iter {                items: Vec::new().into_iter(),                index_changes: Vec::new(),                out: None,            };            let mut out = Outcome {                index_worktree: out,                worktree_index: index,                tree_index,                changes: None,                skip_hash,            };            items.extend(                collect                    .items                    .into_iter()                    .filter_map(|item| iter.maybe_keep_index_change(item)),            );            out.changes = (!iter.index_changes.is_empty()).then(|| std::mem::take(&mut iter.index_changes));            iter.items = items.into_iter();            iter.out = Some(out);            Ok(iter)        }    }} /// The error returned for each item returned by [`Iter`].#[derive(Debug, thiserror::Error)]#[expect(missing_docs)]pub enum Error {    #[error(transparent)]    IndexWorktree(#[from] index_worktree::Error),    #[error(transparent)]    TreeIndex(#[from] tree_index::Error),} impl Iterator for Iter {    type Item = Result<Item, Error>;     fn next(&mut self) -> Option<Self::Item> {        #[cfg(feature = "parallel")]        loop {            let (rx, _join_worktree, _join_tree) = self.rx_and_join.as_ref()?;            match rx.recv_timeout(std::time::Duration::from_millis(25)) {                Ok(item) => {                    if let Some(item) = self.maybe_keep_index_change(item) {                        break Some(Ok(item));                    }                    continue;                }                // NOTE: this isn't necessary when index::from-tree also supports interrupts. As it stands,                //       on big repositories it can go up to 500ms which aren't interruptible, so this is another                //       way to not wait for this. Once it can be interrupted, this won't be needed anymore.                Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {                    if self.should_interrupt.load(Ordering::SeqCst) {                        return None;                    }                }                Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => {                    let (_rx, worktree_handle, tree_handle) = self.rx_and_join.take()?;                    let tree_index = if let Some(handle) = tree_handle {                        match handle.join().expect("no panic") {                            Ok(out) => Some(out),                            Err(err) => break Some(Err(err.into())),                        }                    } else {                        None                    };                    break match worktree_handle.join().expect("no panic") {                        Ok(mut out) => {                            out.changes = Some(std::mem::take(&mut self.index_changes));                            out.tree_index = tree_index;                            self.out = Some(out);                            None                        }                        Err(err) => Some(Err(err.into())),                    };                }            }        }        #[cfg(not(feature = "parallel"))]        self.items.next().map(Ok)    }} /// Accessimpl Iter {    /// Return the outcome of the iteration, or `None` if the iterator isn't fully consumed.    pub fn outcome_mut(&mut self) -> Option<&mut Outcome> {        self.out.as_mut()    }     /// Turn the iterator into the iteration outcome, which is `None` on error or if the iteration    /// isn't complete.    pub fn into_outcome(mut self) -> Option<Outcome> {        self.out.take()    }} impl Iter {    fn maybe_keep_index_change(&mut self, item: Item) -> Option<Item> {        match item {            Item::IndexWorktree(index_worktree::Item::Modification {                status: EntryStatus::NeedsUpdate(stat),                entry_index,                ..            }) => {                self.index_changes.push((entry_index, ApplyChange::NewStat(stat)));                return None;            }            Item::IndexWorktree(index_worktree::Item::Modification {                status:                    EntryStatus::Change(Change::Modification {                        set_entry_stat_size_zero,                        ..                    }),                entry_index,                ..            }) if set_entry_stat_size_zero => {                self.index_changes.push((entry_index, ApplyChange::SetSizeToZero));            }            _ => {}        }        Some(item)    }} #[cfg(feature = "parallel")]impl Drop for Iter {    fn drop(&mut self) {        crate::util::parallel_iter_drop(self.rx_and_join.take(), &self.should_interrupt);    }} struct Collect {    #[cfg(feature = "parallel")]    tx: std::sync::mpsc::Sender<Item>,    #[cfg(not(feature = "parallel"))]    items: Vec<Item>,} impl<'index> gix_status::index_as_worktree_with_renames::VisitEntry<'index> for Collect {    type ContentChange =        <gix_status::index_as_worktree::traits::FastEq as gix_status::index_as_worktree::traits::CompareBlobs>::Output;    type SubmoduleStatus = <BuiltinSubmoduleStatus as gix_status::index_as_worktree::traits::SubmoduleStatus>::Output;     fn visit_entry(        &mut self,        entry: gix_status::index_as_worktree_with_renames::Entry<'index, Self::ContentChange, Self::SubmoduleStatus>,    ) {        // NOTE: we assume that the receiver triggers interruption so the operation will stop if the receiver is down.        let item = Item::IndexWorktree(entry.into());        #[cfg(feature = "parallel")]        self.tx.send(item).ok();        #[cfg(not(feature = "parallel"))]        self.items.push(item);    }}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t