use bstr::{BString, ByteSlice}; use gix_glob::pattern::Case; use crate::{stack::State, PathIdMapping}; #[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, /// 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>, /// 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, } /// #[cfg(feature = "attributes")] pub mod attributes; /// pub mod ignore; /// Initialization impl 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, attributes: Attributes) -> Self { State::CreateDirectoryAndAttributesStack { unlink_on_collision, 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 } } } /// Utilities impl 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 { 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() == 0 || entry.stage() == 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") } } } }