174 lines · 7.3 KB · Rust
RawDownload
use std::path::PathBuf; use bstr::{BString, ByteSlice};use gix_glob::pattern::Case; use crate::{cache::State, PathIdMapping}; type AttributeMatchGroup = gix_attributes::Search;type IgnoreMatchGroup = gix_ignore::Search; /// State related to attributes associated with files in the repository.#[derive(Default, Clone)]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<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)]#[allow(unused)]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,} ///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.    pub fn for_checkout(unlink_on_collision: bool, attributes: Attributes) -> Self {        State::CreateDirectoryAndAttributesStack {            unlink_on_collision,            attributes,        }    }     /// Configure a state for adding files, with support for ignore files and attribute files.    pub fn for_add(attributes: Attributes, ignore: Ignore) -> Self {        State::AttributesAndIgnoreStack { attributes, ignore }    }     /// Configure a state for status retrieval, which needs access to ignore files only.    pub fn for_status(ignore: Ignore) -> Self {        State::IgnoreStack(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,        ignore_source: ignore::Source,        case: Case,    ) -> Vec<PathIdMapping> {        let a1_backing;        let a2_backing;        let names = match self {            State::IgnoreStack(v) => {                a1_backing = [(v.exclude_file_name_for_directories.as_bytes().as_bstr(), true)];                a1_backing.as_ref()            }            State::AttributesAndIgnoreStack { ignore, .. } => {                a2_backing = [                    (ignore.exclude_file_name_for_directories.as_bytes().as_bstr(), true),                    (".gitattributes".into(), false),                ];                a2_backing.as_ref()            }            State::CreateDirectoryAndAttributesStack { .. } => {                a1_backing = [(".gitattributes".into(), true)];                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() == 0 || entry.stage() == 2) {                    let basename = path.rfind_byte(b'/').map_or(path, |pos| path[pos + 1..].as_bstr());                    let is_ignore = 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 is_ignore {                        match ignore_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,            State::AttributesAndIgnoreStack { ignore, .. } => ignore,            State::CreateDirectoryAndAttributesStack { .. } => {                unreachable!("BUG: must not try to check excludes without it being setup")            }        }    }     pub(crate) fn attributes_or_panic(&self) -> &Attributes {        match self {            State::AttributesAndIgnoreStack { attributes, .. }            | State::CreateDirectoryAndAttributesStack { attributes, .. } => attributes,            State::IgnoreStack(_) => {                unreachable!("BUG: must not try to check excludes without it being setup")            }        }    }}