summaryrefslogtreecommitdiffstats
path: root/vendor/gix-attributes/src/search
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gix-attributes/src/search')
-rw-r--r--vendor/gix-attributes/src/search/attributes.rs217
-rw-r--r--vendor/gix-attributes/src/search/mod.rs150
-rw-r--r--vendor/gix-attributes/src/search/outcome.rs365
-rw-r--r--vendor/gix-attributes/src/search/refmap.rs52
4 files changed, 784 insertions, 0 deletions
diff --git a/vendor/gix-attributes/src/search/attributes.rs b/vendor/gix-attributes/src/search/attributes.rs
new file mode 100644
index 000000000..a34ae8b3e
--- /dev/null
+++ b/vendor/gix-attributes/src/search/attributes.rs
@@ -0,0 +1,217 @@
+use std::path::{Path, PathBuf};
+
+use bstr::{BStr, ByteSlice};
+use gix_glob::search::{pattern, Pattern};
+
+use super::Attributes;
+use crate::{
+ search::{Assignments, MetadataCollection, Outcome, TrackedAssignment, Value},
+ Search,
+};
+
+/// Instantiation and initialization.
+impl Search {
+ /// Create a search instance preloaded with *built-ins* followed by attribute `files` from various global locations.
+ ///
+ /// See [`Source`][crate::Source] for a way to obtain these paths.
+ ///
+ /// Note that parsing is lenient and errors are logged.
+ ///
+ /// * `buf` is used to read `files` from disk which will be ignored if they do not exist.
+ /// * `collection` will be updated with information necessary to perform lookups later.
+ pub fn new_globals(
+ files: impl IntoIterator<Item = impl Into<PathBuf>>,
+ buf: &mut Vec<u8>,
+ collection: &mut MetadataCollection,
+ ) -> std::io::Result<Self> {
+ let mut group = Self::default();
+ group.add_patterns_buffer(b"[attr]binary -diff -merge -text", "[builtin]", None, collection);
+
+ for path in files.into_iter() {
+ group.add_patterns_file(path, true, None, buf, collection)?;
+ }
+ Ok(group)
+ }
+}
+
+/// Mutation
+impl Search {
+ /// Add the given file at `source` to our patterns if it exists, otherwise do nothing.
+ /// Update `collection` with newly added attribute names.
+ /// If a `root` is provided, it's not considered a global file anymore.
+ /// Returns `true` if the file was added, or `false` if it didn't exist.
+ pub fn add_patterns_file(
+ &mut self,
+ source: impl Into<PathBuf>,
+ follow_symlinks: bool,
+ root: Option<&Path>,
+ buf: &mut Vec<u8>,
+ collection: &mut MetadataCollection,
+ ) -> std::io::Result<bool> {
+ let was_added = gix_glob::search::add_patterns_file(&mut self.patterns, source, follow_symlinks, root, buf)?;
+ if was_added {
+ collection.update_from_list(self.patterns.last_mut().expect("just added"));
+ }
+ Ok(was_added)
+ }
+ /// Add patterns as parsed from `bytes`, providing their `source` path and possibly their `root` path, the path they
+ /// are relative to. This also means that `source` is contained within `root` if `root` is provided.
+ pub fn add_patterns_buffer(
+ &mut self,
+ bytes: &[u8],
+ source: impl Into<PathBuf>,
+ root: Option<&Path>,
+ collection: &mut MetadataCollection,
+ ) {
+ self.patterns.push(pattern::List::from_bytes(bytes, source, root));
+ collection.update_from_list(self.patterns.last_mut().expect("just added"));
+ }
+
+ /// Pop the last attribute patterns list from our queue.
+ pub fn pop_pattern_list(&mut self) -> Option<gix_glob::search::pattern::List<Attributes>> {
+ self.patterns.pop()
+ }
+}
+
+/// Access and matching
+impl Search {
+ /// Match `relative_path`, a path relative to the repository, while respective `case`-sensitivity and write them to `out`
+ /// Return `true` if at least one pattern matched.
+ pub fn pattern_matching_relative_path<'a, 'b>(
+ &'a self,
+ relative_path: impl Into<&'b BStr>,
+ case: gix_glob::pattern::Case,
+ out: &mut Outcome,
+ ) -> bool {
+ let relative_path = relative_path.into();
+ let basename_pos = relative_path.rfind(b"/").map(|p| p + 1);
+ let mut has_match = false;
+ self.patterns.iter().rev().any(|pl| {
+ has_match |= pattern_matching_relative_path(pl, relative_path, basename_pos, case, out);
+ out.is_done()
+ });
+ has_match
+ }
+
+ /// Return the amount of pattern lists contained in this instance.
+ pub fn num_pattern_lists(&self) -> usize {
+ self.patterns.len()
+ }
+}
+
+impl Pattern for Attributes {
+ type Value = Value;
+
+ fn bytes_to_patterns(bytes: &[u8], source: &std::path::Path) -> Vec<pattern::Mapping<Self::Value>> {
+ fn into_owned_assignments<'a>(
+ attrs: impl Iterator<Item = Result<crate::AssignmentRef<'a>, crate::name::Error>>,
+ ) -> Option<Assignments> {
+ let res = attrs
+ .map(|res| {
+ res.map(|a| TrackedAssignment {
+ id: Default::default(),
+ inner: a.to_owned(),
+ })
+ })
+ .collect::<Result<Assignments, _>>();
+ match res {
+ Ok(res) => Some(res),
+ Err(err) => {
+ log::warn!("{}", err);
+ None
+ }
+ }
+ }
+
+ crate::parse(bytes)
+ .filter_map(|res| match res {
+ Ok(pattern) => Some(pattern),
+ Err(err) => {
+ log::warn!("{}: {}", source.display(), err);
+ None
+ }
+ })
+ .filter_map(|(pattern_kind, assignments, line_number)| {
+ let (pattern, value) = match pattern_kind {
+ crate::parse::Kind::Macro(macro_name) => (
+ gix_glob::Pattern {
+ text: macro_name.as_str().into(),
+ mode: macro_mode(),
+ first_wildcard_pos: None,
+ },
+ Value::MacroAssignments {
+ id: Default::default(),
+ assignments: into_owned_assignments(assignments)?,
+ },
+ ),
+ crate::parse::Kind::Pattern(p) => (
+ (!p.is_negative()).then_some(p)?,
+ Value::Assignments(into_owned_assignments(assignments)?),
+ ),
+ };
+ pattern::Mapping {
+ pattern,
+ value,
+ sequence_number: line_number,
+ }
+ .into()
+ })
+ .collect()
+ }
+
+ fn may_use_glob_pattern(pattern: &gix_glob::Pattern) -> bool {
+ pattern.mode != macro_mode()
+ }
+}
+
+fn macro_mode() -> gix_glob::pattern::Mode {
+ gix_glob::pattern::Mode::all()
+}
+
+/// Append all matches of patterns matching `relative_path` to `out`,
+/// providing a pre-computed `basename_pos` which is the starting position of the basename of `relative_path`.
+/// `case` specifies whether cases should be folded during matching or not.
+/// `is_dir` is true if `relative_path` is a directory.
+/// Return `true` if at least one pattern matched.
+#[allow(unused_variables)]
+fn pattern_matching_relative_path(
+ list: &gix_glob::search::pattern::List<Attributes>,
+ relative_path: &BStr,
+ basename_pos: Option<usize>,
+ case: gix_glob::pattern::Case,
+ out: &mut Outcome,
+) -> bool {
+ let (relative_path, basename_start_pos) =
+ match list.strip_base_handle_recompute_basename_pos(relative_path, basename_pos, case) {
+ Some(r) => r,
+ None => return false,
+ };
+ let cur_len = out.remaining();
+ 'outer: for pattern::Mapping {
+ pattern,
+ value,
+ sequence_number,
+ } in list
+ .patterns
+ .iter()
+ .rev()
+ .filter(|pm| Attributes::may_use_glob_pattern(&pm.pattern))
+ {
+ let value: &Value = value;
+ let attrs = match value {
+ Value::MacroAssignments { .. } => {
+ unreachable!("we can't match on macros as they have no pattern")
+ }
+ Value::Assignments(attrs) => attrs,
+ };
+ if out.has_unspecified_attributes(attrs.iter().map(|attr| attr.id))
+ && pattern.matches_repo_relative_path(relative_path, basename_start_pos, None, case)
+ {
+ let all_filled = out.fill_attributes(attrs.iter(), pattern, list.source.as_ref(), *sequence_number);
+ if all_filled {
+ break 'outer;
+ }
+ }
+ }
+ cur_len != out.remaining()
+}
diff --git a/vendor/gix-attributes/src/search/mod.rs b/vendor/gix-attributes/src/search/mod.rs
new file mode 100644
index 000000000..e70c3b8b1
--- /dev/null
+++ b/vendor/gix-attributes/src/search/mod.rs
@@ -0,0 +1,150 @@
+use std::collections::HashMap;
+
+use kstring::KString;
+use smallvec::SmallVec;
+
+use crate::{Assignment, AssignmentRef};
+
+mod attributes;
+mod outcome;
+mod refmap;
+pub(crate) use refmap::RefMap;
+
+/// A typically sized list of attributes.
+pub type Assignments = SmallVec<[TrackedAssignment; AVERAGE_NUM_ATTRS]>;
+
+/// A value of a [pattern mapping][gix_glob::search::pattern::Mapping],
+/// which is either a macro definition or a set of attributes.
+#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
+pub enum Value {
+ /// A macro, whose name resolves to the contained assignments. Note that the name is the pattern of the mapping itself.
+ MacroAssignments {
+ /// The id of the macro itself, which is both an attribute as well as a set of additional attributes into which the macro
+ /// resolves
+ id: AttributeId,
+ /// The attributes or assignments that the macro resolves to.
+ assignments: Assignments,
+ },
+ /// A set of assignments which are the attributes themselves.
+ Assignments(Assignments),
+}
+
+/// A way to have an assignment (`attr=value`) but also associated it with an id that allows perfect mapping
+/// to tracking information.
+/// Note that the order is produced after the files are parsed as global ordering is needed that goes beyond the scope of a
+/// single `Search` instance.
+#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
+pub struct TrackedAssignment {
+ /// The order of the assignment.
+ pub id: AttributeId,
+ /// The actual assignment information.
+ pub inner: Assignment,
+}
+
+/// An implementation of the [`Pattern`][gix_glob::search::Pattern] trait for attributes.
+#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Default)]
+pub struct Attributes;
+
+/// Describes a matching pattern with
+#[derive(Clone, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)]
+pub struct Match<'a> {
+ /// The glob pattern itself, like `/target/*`.
+ pub pattern: &'a gix_glob::Pattern,
+ /// The key=value pair of the attribute that matched at the pattern. There can be multiple matches per pattern.
+ pub assignment: AssignmentRef<'a>,
+ /// Additional information about the kind of match.
+ pub kind: MatchKind,
+ /// Information about the location of the match.
+ pub location: MatchLocation<'a>,
+}
+
+/// Describes in which what file and line the match was found.
+#[derive(Clone, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)]
+pub struct MatchLocation<'a> {
+ /// The path to the source from which the pattern was loaded, or `None` if it was specified by other means.
+ pub source: Option<&'a std::path::Path>,
+ /// The line at which the pattern was found in its `source` file, or the occurrence in which it was provided.
+ pub sequence_number: usize,
+}
+
+/// The kind of attribute within the context of a [match][Match].
+#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)]
+pub enum MatchKind {
+ /// A attribute.
+ Attribute {
+ /// The location of the macro which referred to it the list with all in-order attributes and macros, or `None` if
+ /// this is attribute wasn't resolved.
+ ///
+ /// Use [`Outcome::match_by_id()`] to retrieve the macro.
+ macro_id: Option<AttributeId>,
+ },
+ /// The attribute is a macro, which will resolve into one or more attributes or macros.
+ Macro {
+ /// The location of the parent macro which referred to this one in the list with all in-order attributes and macros,
+ /// or `None` if this is macro wasn't resolved by another one.
+ ///
+ /// Use [`Outcome::match_by_id()`] to retrieve the parent.
+ parent_macro_id: Option<AttributeId>,
+ },
+}
+
+/// The result of a search, containing all matching attributes.
+#[derive(Default)]
+pub struct Outcome {
+ /// The list of all available attributes, by ascending order. Each slots index corresponds to an attribute with that order, i.e.
+ /// `arr[attr.id] = <attr info>`.
+ ///
+ /// This list needs to be up-to-date with the search group so all possible attribute names are known.
+ matches_by_id: Vec<Slot>,
+ /// A stack of attributes to use for processing attributes of matched patterns and for resolving their macros.
+ attrs_stack: SmallVec<[(AttributeId, Assignment, Option<AttributeId>); 8]>,
+ /// A set of attributes we should limit ourselves to, or empty if we should fill in all attributes, made of
+ selected: SmallVec<[(KString, Option<AttributeId>); AVERAGE_NUM_ATTRS]>,
+ /// storage for all patterns we have matched so far (in order to avoid referencing them, we copy them, but only once).
+ patterns: RefMap<gix_glob::Pattern>,
+ /// storage for all assignments we have matched so far (in order to avoid referencing them, we copy them, but only once).
+ assignments: RefMap<Assignment>,
+ /// storage for all source paths we have matched so far (in order to avoid referencing them, we copy them, but only once).
+ source_paths: RefMap<std::path::PathBuf>,
+ /// The amount of attributes that still need to be set, or `None` if this outcome is consumed which means it
+ /// needs to be re-initialized.
+ remaining: Option<usize>,
+}
+
+#[derive(Default, Clone)]
+struct Slot {
+ r#match: Option<outcome::Match>,
+ /// A list of all assignments, being an empty list for non-macro attributes, or all assignments (with order) for macros.
+ /// It's used to resolve macros.
+ macro_attributes: Assignments,
+}
+
+/// A type to denote an id of an attribute assignment for uniquely identifying each attribute or assignment.
+#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
+pub struct AttributeId(pub usize);
+
+impl Default for AttributeId {
+ fn default() -> Self {
+ AttributeId(usize::MAX)
+ }
+}
+
+/// A utility type to collect metadata for each attribute, unified by its name.
+#[derive(Clone, Debug, Default)]
+pub struct MetadataCollection {
+ /// A mapping of an attribute or macro name to its order, that is the time when it was *first* seen.
+ ///
+ /// This is the inverse of the order attributes are searched.
+ name_to_meta: HashMap<KString, Metadata>,
+}
+
+/// Metadata associated with an attribute or macro name.
+#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
+pub struct Metadata {
+ /// The id to uniquely identify an attribute in the [MetadataCollection].
+ pub id: AttributeId,
+ /// If non-zero in length, this entry belongs to a macro which resolves to these attribute names.
+ pub macro_attributes: Assignments,
+}
+
+const AVERAGE_NUM_ATTRS: usize = 3;
diff --git a/vendor/gix-attributes/src/search/outcome.rs b/vendor/gix-attributes/src/search/outcome.rs
new file mode 100644
index 000000000..5d5a26b44
--- /dev/null
+++ b/vendor/gix-attributes/src/search/outcome.rs
@@ -0,0 +1,365 @@
+use bstr::{BString, ByteSlice};
+use gix_glob::Pattern;
+use kstring::{KString, KStringRef};
+
+use crate::search::refmap::RefMapKey;
+use crate::{
+ search::{
+ Assignments, AttributeId, Attributes, MatchKind, Metadata, MetadataCollection, Outcome, TrackedAssignment,
+ Value,
+ },
+ AssignmentRef, NameRef, StateRef,
+};
+
+/// Initialization
+impl Outcome {
+ /// Initialize this instance to collect outcomes for all names in `collection`, which represents all possible attributes
+ /// or macros we may visit, and [`reset`][Self::reset()] it unconditionally.
+ ///
+ /// This must be called after each time `collection` changes.
+ pub fn initialize(&mut self, collection: &MetadataCollection) {
+ if self.matches_by_id.len() != collection.name_to_meta.len() {
+ let global_num_attrs = collection.name_to_meta.len();
+
+ self.matches_by_id.resize(global_num_attrs, Default::default());
+
+ // NOTE: This works only under the assumption that macros remain defined.
+ for (order, macro_attributes) in collection.iter().filter_map(|(_, meta)| {
+ (!meta.macro_attributes.is_empty()).then_some((meta.id.0, &meta.macro_attributes))
+ }) {
+ self.matches_by_id[order].macro_attributes = macro_attributes.clone()
+ }
+ }
+ self.reset();
+ }
+
+ /// Like [`initialize()`][Self::initialize()], but limits the set of attributes to look for and fill in
+ /// to `attribute_names`.
+ /// Users of this instance should prefer to limit their search as this would allow it to finish earlier.
+ ///
+ /// Note that `attribute_names` aren't validated to be valid names here, as invalid names definitely will always be unspecified.
+ pub fn initialize_with_selection<'a>(
+ &mut self,
+ collection: &MetadataCollection,
+ attribute_names: impl IntoIterator<Item = impl Into<KStringRef<'a>>>,
+ ) {
+ self.initialize(collection);
+
+ self.selected.clear();
+ self.selected.extend(attribute_names.into_iter().map(|name| {
+ let name = name.into();
+ (
+ name.to_owned(),
+ collection.name_to_meta.get(name.as_str()).map(|meta| meta.id),
+ )
+ }));
+ self.reset_remaining();
+ }
+
+ /// Prepare for a new search over the known set of attributes by resetting our state.
+ pub fn reset(&mut self) {
+ self.matches_by_id.iter_mut().for_each(|item| item.r#match = None);
+ self.attrs_stack.clear();
+ self.reset_remaining();
+ }
+
+ fn reset_remaining(&mut self) {
+ self.remaining = Some(if self.selected.is_empty() {
+ self.matches_by_id.len()
+ } else {
+ self.selected.iter().filter(|(_name, id)| id.is_some()).count()
+ });
+ }
+}
+
+/// Access
+impl Outcome {
+ /// Return an iterator over all filled attributes we were initialized with.
+ ///
+ /// ### Note
+ ///
+ /// If [`initialize_with_selection`][Self::initialize_with_selection()] was used,
+ /// use [`iter_selected()`][Self::iter_selected()] instead.
+ ///
+ /// ### Deviation
+ ///
+ /// It's possible that the order in which the attribute are returned (if not limited to a set of attributes) isn't exactly
+ /// the same as what `git` provides.
+ /// Ours is in order of declaration, whereas `git` seems to list macros first somehow. Since the values are the same, this
+ /// shouldn't be an issue.
+ pub fn iter(&self) -> impl Iterator<Item = crate::search::Match<'_>> {
+ self.matches_by_id
+ .iter()
+ .filter_map(|item| item.r#match.as_ref().map(|m| m.to_outer(self)))
+ }
+
+ /// Iterate over all matches of the attribute selection in their original order.
+ ///
+ /// This only yields values if this instance was initialized with [`Outcome::initialize_with_selection()`].
+ pub fn iter_selected(&self) -> impl Iterator<Item = crate::search::Match<'_>> {
+ static DUMMY: Pattern = Pattern {
+ text: BString::new(Vec::new()),
+ mode: gix_glob::pattern::Mode::empty(),
+ first_wildcard_pos: None,
+ };
+ self.selected.iter().map(|(name, id)| {
+ id.and_then(|id| self.matches_by_id[id.0].r#match.as_ref().map(|m| m.to_outer(self)))
+ .unwrap_or_else(|| crate::search::Match {
+ pattern: &DUMMY,
+ assignment: AssignmentRef {
+ name: NameRef::try_from(name.as_bytes().as_bstr())
+ .unwrap_or_else(|_| NameRef("invalid".into())),
+ state: StateRef::Unspecified,
+ },
+ kind: MatchKind::Attribute { macro_id: None },
+ location: crate::search::MatchLocation {
+ source: None,
+ sequence_number: 0,
+ },
+ })
+ })
+ }
+
+ /// Obtain a match by the order of its attribute, if the order exists in our initialized attribute list and there was a match.
+ pub fn match_by_id(&self, id: AttributeId) -> Option<crate::search::Match<'_>> {
+ self.matches_by_id
+ .get(id.0)
+ .and_then(|m| m.r#match.as_ref().map(|m| m.to_outer(self)))
+ }
+
+ /// Return `true` if there is nothing more to be done as all attributes were filled.
+ pub fn is_done(&self) -> bool {
+ self.remaining() == 0
+ }
+}
+
+/// Mutation
+impl Outcome {
+ /// Fill all `attrs` and resolve them recursively if they are macros. Return `true` if there is no attribute left to be resolved and
+ /// we are totally done.
+ /// `pattern` is what matched a patch and is passed for contextual information,
+ /// providing `sequence_number` and `source` as well.
+ pub(crate) fn fill_attributes<'a>(
+ &mut self,
+ attrs: impl Iterator<Item = &'a TrackedAssignment>,
+ pattern: &gix_glob::Pattern,
+ source: Option<&std::path::PathBuf>,
+ sequence_number: usize,
+ ) -> bool {
+ self.attrs_stack.extend(attrs.filter_map(|attr| {
+ self.matches_by_id[attr.id.0]
+ .r#match
+ .is_none()
+ .then(|| (attr.id, attr.inner.clone(), None))
+ }));
+ while let Some((id, assignment, parent_order)) = self.attrs_stack.pop() {
+ let slot = &mut self.matches_by_id[id.0];
+ if slot.r#match.is_some() {
+ continue;
+ }
+ // Let's be explicit - this is only non-empty for macros.
+ let is_macro = !slot.macro_attributes.is_empty();
+
+ slot.r#match = Some(Match {
+ pattern: self.patterns.insert(pattern),
+ assignment: self.assignments.insert_owned(assignment),
+ kind: if is_macro {
+ MatchKind::Macro {
+ parent_macro_id: parent_order,
+ }
+ } else {
+ MatchKind::Attribute { macro_id: parent_order }
+ },
+ location: MatchLocation {
+ source: source.map(|path| self.source_paths.insert(path)),
+ sequence_number,
+ },
+ });
+ if self.reduce_and_check_if_done(id) {
+ return true;
+ }
+
+ if is_macro {
+ // TODO(borrowchk): one fine day we should be able to re-borrow `slot` without having to redo the array access.
+ let slot = &self.matches_by_id[id.0];
+ self.attrs_stack.extend(slot.macro_attributes.iter().filter_map(|attr| {
+ self.matches_by_id[attr.id.0]
+ .r#match
+ .is_none()
+ .then(|| (attr.id, attr.inner.clone(), Some(id)))
+ }));
+ }
+ }
+ false
+ }
+}
+
+impl Outcome {
+ /// Given a list of `attrs` by order, return true if at least one of them is not set
+ pub(crate) fn has_unspecified_attributes(&self, mut attrs: impl Iterator<Item = AttributeId>) -> bool {
+ attrs.any(|order| self.matches_by_id[order.0].r#match.is_none())
+ }
+ /// Return the amount of attributes haven't yet been found.
+ ///
+ /// If this number reaches 0, then the search can be stopped as there is nothing more to fill in.
+ pub(crate) fn remaining(&self) -> usize {
+ self.remaining
+ .expect("BUG: instance must be initialized for each search set")
+ }
+
+ fn reduce_and_check_if_done(&mut self, attr: AttributeId) -> bool {
+ if self.selected.is_empty()
+ || self
+ .selected
+ .iter()
+ .any(|(_name, id)| id.map_or(false, |id| id == attr))
+ {
+ *self.remaining.as_mut().expect("initialized") -= 1;
+ }
+ self.is_done()
+ }
+}
+
+/// Mutation
+impl MetadataCollection {
+ /// Assign order ids to each attribute either in macros (along with macros themselves) or attributes of patterns, and store
+ /// them in this collection.
+ ///
+ /// Must be called before querying matches.
+ pub fn update_from_list(&mut self, list: &mut gix_glob::search::pattern::List<Attributes>) {
+ for pattern in &mut list.patterns {
+ match &mut pattern.value {
+ Value::MacroAssignments { id: order, assignments } => {
+ *order = self.id_for_macro(
+ pattern
+ .pattern
+ .text
+ .to_str()
+ .expect("valid macro names are always UTF8 and this was verified"),
+ assignments,
+ );
+ }
+ Value::Assignments(assignments) => {
+ self.assign_order_to_attributes(assignments);
+ }
+ }
+ }
+ }
+}
+
+/// Access
+impl MetadataCollection {
+ /// Return an iterator over the contents of the map in an easy-to-consume form.
+ pub fn iter(&self) -> impl Iterator<Item = (&str, &Metadata)> {
+ self.name_to_meta.iter().map(|(k, v)| (k.as_str(), v))
+ }
+}
+
+impl MetadataCollection {
+ pub(crate) fn id_for_macro(&mut self, name: &str, attrs: &mut Assignments) -> AttributeId {
+ let order = match self.name_to_meta.get_mut(name) {
+ Some(meta) => meta.id,
+ None => {
+ let order = AttributeId(self.name_to_meta.len());
+ self.name_to_meta.insert(
+ KString::from_ref(name),
+ Metadata {
+ id: order,
+ macro_attributes: Default::default(),
+ },
+ );
+ order
+ }
+ };
+
+ self.assign_order_to_attributes(attrs);
+ self.name_to_meta.get_mut(name).expect("just added").macro_attributes = attrs.clone();
+
+ order
+ }
+ pub(crate) fn id_for_attribute(&mut self, name: &str) -> AttributeId {
+ match self.name_to_meta.get(name) {
+ Some(meta) => meta.id,
+ None => {
+ let order = AttributeId(self.name_to_meta.len());
+ self.name_to_meta.insert(KString::from_ref(name), order.into());
+ order
+ }
+ }
+ }
+ pub(crate) fn assign_order_to_attributes(&mut self, attributes: &mut [TrackedAssignment]) {
+ for TrackedAssignment {
+ id: order,
+ inner: crate::Assignment { name, .. },
+ } in attributes
+ {
+ *order = self.id_for_attribute(&name.0);
+ }
+ }
+}
+
+impl From<AttributeId> for Metadata {
+ fn from(order: AttributeId) -> Self {
+ Metadata {
+ id: order,
+ macro_attributes: Default::default(),
+ }
+ }
+}
+
+impl MatchKind {
+ /// return the id of the macro that resolved us, or `None` if that didn't happen.
+ pub fn source_id(&self) -> Option<AttributeId> {
+ match self {
+ MatchKind::Attribute { macro_id: id } | MatchKind::Macro { parent_macro_id: id } => *id,
+ }
+ }
+}
+
+/// A version of `Match` without references.
+#[derive(Clone, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)]
+pub struct Match {
+ /// The glob pattern itself, like `/target/*`.
+ pub pattern: RefMapKey,
+ /// The key=value pair of the attribute that matched at the pattern. There can be multiple matches per pattern.
+ pub assignment: RefMapKey,
+ /// Additional information about the kind of match.
+ pub kind: MatchKind,
+ /// Information about the location of the match.
+ pub location: MatchLocation,
+}
+
+impl Match {
+ fn to_outer<'a>(&self, out: &'a Outcome) -> crate::search::Match<'a> {
+ crate::search::Match {
+ pattern: out.patterns.resolve(self.pattern).expect("pattern still present"),
+ assignment: out
+ .assignments
+ .resolve(self.assignment)
+ .expect("assignment present")
+ .as_ref(),
+ kind: self.kind,
+ location: self.location.to_outer(out),
+ }
+ }
+}
+
+/// A version of `MatchLocation` without references.
+#[derive(Clone, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)]
+pub struct MatchLocation {
+ /// The path to the source from which the pattern was loaded, or `None` if it was specified by other means.
+ pub source: Option<RefMapKey>,
+ /// The line at which the pattern was found in its `source` file, or the occurrence in which it was provided.
+ pub sequence_number: usize,
+}
+
+impl MatchLocation {
+ fn to_outer<'a>(&self, out: &'a Outcome) -> crate::search::MatchLocation<'a> {
+ crate::search::MatchLocation {
+ source: self
+ .source
+ .and_then(|source| out.source_paths.resolve(source).map(|p| p.as_path())),
+ sequence_number: self.sequence_number,
+ }
+ }
+}
diff --git a/vendor/gix-attributes/src/search/refmap.rs b/vendor/gix-attributes/src/search/refmap.rs
new file mode 100644
index 000000000..3dc51265c
--- /dev/null
+++ b/vendor/gix-attributes/src/search/refmap.rs
@@ -0,0 +1,52 @@
+//! A utility to store objects by identity, which deduplicates them while avoiding lifetimes.
+//!
+//! We chose to use hashing/identity over pointers as it's possible that different objects end up in the same memory location,
+//! which would create obscure bugs. The same could happen with hash collisions, but they these are designed to be less likely.
+use std::collections::btree_map::Entry;
+use std::collections::hash_map::DefaultHasher;
+use std::collections::BTreeMap;
+use std::hash::{Hash, Hasher};
+
+pub(crate) type RefMapKey = u64;
+pub(crate) struct RefMap<T>(BTreeMap<RefMapKey, T>);
+
+impl<T> Default for RefMap<T> {
+ fn default() -> Self {
+ RefMap(Default::default())
+ }
+}
+
+impl<T> RefMap<T>
+where
+ T: Hash + Clone,
+{
+ pub(crate) fn insert(&mut self, value: &T) -> RefMapKey {
+ let mut s = DefaultHasher::new();
+ value.hash(&mut s);
+ let key = s.finish();
+ match self.0.entry(key) {
+ Entry::Vacant(e) => {
+ e.insert(value.clone());
+ key
+ }
+ Entry::Occupied(_) => key,
+ }
+ }
+
+ pub(crate) fn insert_owned(&mut self, value: T) -> RefMapKey {
+ let mut s = DefaultHasher::new();
+ value.hash(&mut s);
+ let key = s.finish();
+ match self.0.entry(key) {
+ Entry::Vacant(e) => {
+ e.insert(value);
+ key
+ }
+ Entry::Occupied(_) => key,
+ }
+ }
+
+ pub(crate) fn resolve(&self, key: RefMapKey) -> Option<&T> {
+ self.0.get(&key)
+ }
+}