| 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<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)] |
| #[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, |
| } |
| |
| /// |
| #[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<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() == 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") |
| } |
| } |
| } |
| } |