190 lines · 7.9 KB · Rust
RawDownload
use bstr::{BString, ByteSlice};use gix_glob::pattern::Case; use crate::{PathIdMapping, stack::State}; #[cfg(feature = "attributes")]type AttributeMatchGroup = gix_attributes::Search;type IgnoreMatchGroup = gix_ignore::Search; /// State related to attributes associated with files in the repository.#[derive(Default, Clone)]#[cfg(feature = "attributes")]pub struct Attributes {    /// Attribute patterns which aren't tied to the repository root, hence are global, they contribute first.    globals: AttributeMatchGroup,    /// Attribute patterns that match the currently set directory (in the stack).    ///    /// Note that the root-level file is always loaded, if present, followed by, the `$GIT_DIR/info/attributes`, if present, based    /// on the location of the `info_attributes` file.    stack: AttributeMatchGroup,    /// The first time we push the root, we have to load additional information from this file if it exists along with the root attributes    /// file if possible, and keep them there throughout.    info_attributes: Option<std::path::PathBuf>,    /// A lookup table to accelerate searches.    collection: gix_attributes::search::MetadataCollection,    /// Where to read `.gitattributes` data from.    source: attributes::Source,} /// State related to the exclusion of files, supporting static overrides and globals, along with a stack of dynamically read/// ignore files from disk or from the index each time the directory changes.#[derive(Default, Clone)]pub struct Ignore {    /// Ignore patterns passed as overrides to everything else, typically passed on the command-line and the first patterns to    /// be consulted.    overrides: IgnoreMatchGroup,    /// Ignore patterns that match the currently set director (in the stack), which is pushed and popped as needed.    stack: IgnoreMatchGroup,    /// Ignore patterns which aren't tied to the repository root, hence are global. They are consulted last.    globals: IgnoreMatchGroup,    /// A matching stack of pattern indices which is empty if we have just been initialized to indicate that the    /// currently set directory had a pattern matched. Note that this one could be negated.    /// (index into match groups, index into list of pattern lists, index into pattern list)    matched_directory_patterns_stack: Vec<Option<(usize, usize, usize)>>,    ///  The name of the file to look for in directories.    pub(crate) exclude_file_name_for_directories: BString,    /// Where to read ignore files from    source: ignore::Source,    /// Control how to parse ignore files.    parse: gix_ignore::search::Ignore,} ///#[cfg(feature = "attributes")]pub mod attributes;///pub mod ignore; /// Initializationimpl State {    /// Configure a state to be suitable for checking out files, which only needs access to attribute files read from the index.    #[cfg(feature = "attributes")]    pub fn for_checkout(        unlink_on_collision: bool,        validate: gix_validate::path::component::Options,        attributes: Attributes,    ) -> Self {        State::CreateDirectoryAndAttributesStack {            unlink_on_collision,            validate,            attributes,        }    }     /// Configure a state for adding files, with support for ignore files and attribute files.    #[cfg(feature = "attributes")]    pub fn for_add(attributes: Attributes, ignore: Ignore) -> Self {        State::AttributesAndIgnoreStack { attributes, ignore }    }} /// Utilitiesimpl State {    /// Returns a vec of tuples of relative index paths along with the best usable blob OID for    /// either *ignore* or *attribute* files or both. This allows files to be accessed directly from    /// the object database without the need for a worktree checkout.    ///    /// Note that this method…    /// - ignores entries which aren't blobs.    /// - ignores ignore entries which are not skip-worktree.    /// - within merges, picks 'our' stage both for *ignore* and *attribute* files.    ///    /// * `index` is where we look for suitable files by path in order to obtain their blob hash.    /// * `paths` is the indices storage backend for paths.    /// * `case` determines if the search for files should be case-sensitive or not.    pub fn id_mappings_from_index(        &self,        index: &gix_index::State,        paths: &gix_index::PathStorageRef,        case: Case,    ) -> Vec<PathIdMapping> {        let a1_backing;        #[cfg(feature = "attributes")]        let a2_backing;        let names = match self {            State::IgnoreStack(ignore) => {                a1_backing = [(                    ignore.exclude_file_name_for_directories.as_bytes().as_bstr(),                    Some(ignore.source),                )];                a1_backing.as_ref()            }            #[cfg(feature = "attributes")]            State::AttributesAndIgnoreStack { ignore, .. } => {                a2_backing = [                    (                        ignore.exclude_file_name_for_directories.as_bytes().as_bstr(),                        Some(ignore.source),                    ),                    (".gitattributes".into(), None),                ];                a2_backing.as_ref()            }            #[cfg(feature = "attributes")]            State::CreateDirectoryAndAttributesStack { .. } | State::AttributesStack(_) => {                a1_backing = [(".gitattributes".into(), None)];                a1_backing.as_ref()            }        };         index            .entries()            .iter()            .filter_map(move |entry| {                let path = entry.path_in(paths);                 // Stage 0 means there is no merge going on, stage 2 means it's 'our' side of the merge, but then                // there won't be a stage 0.                if entry.mode == gix_index::entry::Mode::FILE && (entry.stage_raw() == 0 || entry.stage_raw() == 2) {                    let basename = path.rfind_byte(b'/').map_or(path, |pos| path[pos + 1..].as_bstr());                    let ignore_source = names.iter().find_map(|t| {                        match case {                            Case::Sensitive => basename == t.0,                            Case::Fold => basename.eq_ignore_ascii_case(t.0),                        }                        .then_some(t.1)                    })?;                    if let Some(source) = ignore_source {                        match source {                            ignore::Source::IdMapping => {}                            ignore::Source::WorktreeThenIdMappingIfNotSkipped => {                                // See https://github.com/git/git/blob/master/dir.c#L912:L912                                if !entry.flags.contains(gix_index::entry::Flags::SKIP_WORKTREE) {                                    return None;                                }                            }                        }                    }                    Some((path.to_owned(), entry.id))                } else {                    None                }            })            .collect()    }     pub(crate) fn ignore_or_panic(&self) -> &Ignore {        match self {            State::IgnoreStack(v) => v,            #[cfg(feature = "attributes")]            State::AttributesAndIgnoreStack { ignore, .. } => ignore,            #[cfg(feature = "attributes")]            State::AttributesStack(_) | State::CreateDirectoryAndAttributesStack { .. } => {                unreachable!("BUG: must not try to check excludes without it being setup")            }        }    }     #[cfg(feature = "attributes")]    pub(crate) fn attributes_or_panic(&self) -> &Attributes {        match self {            State::AttributesStack(attributes)            | State::AttributesAndIgnoreStack { attributes, .. }            | State::CreateDirectoryAndAttributesStack { attributes, .. } => attributes,            State::IgnoreStack(_) => {                unreachable!("BUG: must not try to check excludes without it being setup")            }        }    }}