Skip to content

Commit 3456c84

Browse files
committed
Merge branch 'attributes-cache'
2 parents 3180142 + 13a070f commit 3456c84

File tree

30 files changed

+1026
-294
lines changed

30 files changed

+1026
-294
lines changed

Diff for: gix-attributes/src/search/attributes.rs

+18-10
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,14 @@ use crate::{
1111

1212
/// Instantiation and initialization.
1313
impl Search {
14-
/// Create a search instance preloaded with *built-ins* as well as attribute `files` from various global locations.
14+
/// Create a search instance preloaded with *built-ins* followed by attribute `files` from various global locations.
15+
///
1516
/// See [`Source`][crate::Source] for a way to obtain these paths.
17+
///
1618
/// Note that parsing is lenient and errors are logged.
17-
/// `buf` is used to read `files` from disk which will be ignored if they do not exist.
18-
/// `collection` will be updated with information necessary to perform lookups later.
19+
///
20+
/// * `buf` is used to read `files` from disk which will be ignored if they do not exist.
21+
/// * `collection` will be updated with information necessary to perform lookups later.
1922
pub fn new_globals(
2023
files: impl IntoIterator<Item = impl Into<PathBuf>>,
2124
buf: &mut Vec<u8>,
@@ -36,7 +39,7 @@ impl Search {
3639
/// Add the given file at `source` to our patterns if it exists, otherwise do nothing.
3740
/// Update `collection` with newly added attribute names.
3841
/// If a `root` is provided, it's not considered a global file anymore.
39-
/// Returns true if the file was added, or false if it didn't exist.
42+
/// Returns `true` if the file was added, or `false` if it didn't exist.
4043
pub fn add_patterns_file(
4144
&mut self,
4245
source: impl Into<PathBuf>,
@@ -63,17 +66,22 @@ impl Search {
6366
self.patterns.push(pattern::List::from_bytes(bytes, source, root));
6467
collection.update_from_list(self.patterns.last_mut().expect("just added"));
6568
}
69+
70+
/// Pop the last attribute patterns list from our queue.
71+
pub fn pop_pattern_list(&mut self) -> Option<gix_glob::search::pattern::List<Attributes>> {
72+
self.patterns.pop()
73+
}
6674
}
6775

6876
/// Access and matching
6977
impl Search {
7078
/// Match `relative_path`, a path relative to the repository, while respective `case`-sensitivity and write them to `out`
71-
/// Return true if at least one pattern matched.
79+
/// Return `true` if at least one pattern matched.
7280
pub fn pattern_matching_relative_path<'a, 'b>(
7381
&'a self,
7482
relative_path: impl Into<&'b BStr>,
7583
case: gix_glob::pattern::Case,
76-
out: &mut Outcome<'a>,
84+
out: &mut Outcome,
7785
) -> bool {
7886
let relative_path = relative_path.into();
7987
let basename_pos = relative_path.rfind(b"/").map(|p| p + 1);
@@ -166,12 +174,12 @@ fn macro_mode() -> gix_glob::pattern::Mode {
166174
/// `is_dir` is true if `relative_path` is a directory.
167175
/// Return `true` if at least one pattern matched.
168176
#[allow(unused_variables)]
169-
fn pattern_matching_relative_path<'a>(
170-
list: &'a gix_glob::search::pattern::List<Attributes>,
177+
fn pattern_matching_relative_path(
178+
list: &gix_glob::search::pattern::List<Attributes>,
171179
relative_path: &BStr,
172180
basename_pos: Option<usize>,
173181
case: gix_glob::pattern::Case,
174-
out: &mut Outcome<'a>,
182+
out: &mut Outcome,
175183
) -> bool {
176184
let (relative_path, basename_start_pos) =
177185
match list.strip_base_handle_recompute_basename_pos(relative_path, basename_pos, case) {
@@ -199,7 +207,7 @@ fn pattern_matching_relative_path<'a>(
199207
if out.has_unspecified_attributes(attrs.iter().map(|attr| attr.id))
200208
&& pattern.matches_repo_relative_path(relative_path, basename_start_pos, None, case)
201209
{
202-
let all_filled = out.fill_attributes(attrs.iter(), pattern, list.source.as_deref(), *sequence_number);
210+
let all_filled = out.fill_attributes(attrs.iter(), pattern, list.source.as_ref(), *sequence_number);
203211
if all_filled {
204212
break 'outer;
205213
}

Diff for: gix-attributes/src/search/mod.rs

+14-6
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ use std::collections::HashMap;
33
use kstring::KString;
44
use smallvec::SmallVec;
55

6-
use crate::Assignment;
6+
use crate::{Assignment, AssignmentRef};
77

88
mod attributes;
99
mod outcome;
10+
mod refmap;
11+
pub(crate) use refmap::RefMap;
1012

1113
/// A typically sized list of attributes.
1214
pub type Assignments = SmallVec<[TrackedAssignment; AVERAGE_NUM_ATTRS]>;
@@ -49,7 +51,7 @@ pub struct Match<'a> {
4951
/// The glob pattern itself, like `/target/*`.
5052
pub pattern: &'a gix_glob::Pattern,
5153
/// The key=value pair of the attribute that matched at the pattern. There can be multiple matches per pattern.
52-
pub assignment: Assignment,
54+
pub assignment: AssignmentRef<'a>,
5355
/// Additional information about the kind of match.
5456
pub kind: MatchKind,
5557
/// Information about the location of the match.
@@ -88,24 +90,30 @@ pub enum MatchKind {
8890

8991
/// The result of a search, containing all matching attributes.
9092
#[derive(Default)]
91-
pub struct Outcome<'pattern> {
93+
pub struct Outcome {
9294
/// The list of all available attributes, by ascending order. Each slots index corresponds to an attribute with that order, i.e.
9395
/// `arr[attr.id] = <attr info>`.
9496
///
9597
/// This list needs to be up-to-date with the search group so all possible attribute names are known.
96-
matches_by_id: Vec<Slot<'pattern>>,
98+
matches_by_id: Vec<Slot>,
9799
/// A stack of attributes to use for processing attributes of matched patterns and for resolving their macros.
98100
attrs_stack: SmallVec<[(AttributeId, Assignment, Option<AttributeId>); 8]>,
99101
/// A set of attributes we should limit ourselves to, or empty if we should fill in all attributes, made of
100102
selected: SmallVec<[(KString, Option<AttributeId>); AVERAGE_NUM_ATTRS]>,
103+
/// storage for all patterns we have matched so far (in order to avoid referencing them, we copy them, but only once).
104+
patterns: RefMap<gix_glob::Pattern>,
105+
/// storage for all assignments we have matched so far (in order to avoid referencing them, we copy them, but only once).
106+
assignments: RefMap<Assignment>,
107+
/// storage for all source paths we have matched so far (in order to avoid referencing them, we copy them, but only once).
108+
source_paths: RefMap<std::path::PathBuf>,
101109
/// The amount of attributes that still need to be set, or `None` if this outcome is consumed which means it
102110
/// needs to be re-initialized.
103111
remaining: Option<usize>,
104112
}
105113

106114
#[derive(Default, Clone)]
107-
struct Slot<'pattern> {
108-
r#match: Option<Match<'pattern>>,
115+
struct Slot {
116+
r#match: Option<outcome::Match>,
109117
/// A list of all assignments, being an empty list for non-macro attributes, or all assignments (with order) for macros.
110118
/// It's used to resolve macros.
111119
macro_attributes: Assignments,

Diff for: gix-attributes/src/search/outcome.rs

+91-42
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
1-
use std::{borrow::Cow, path::Path};
2-
31
use bstr::{BString, ByteSlice};
42
use gix_glob::Pattern;
53
use kstring::{KString, KStringRef};
64

5+
use crate::search::refmap::RefMapKey;
76
use crate::{
87
search::{
9-
Assignments, AttributeId, Attributes, Match, MatchKind, MatchLocation, Metadata, MetadataCollection, Outcome,
10-
TrackedAssignment, Value,
8+
Assignments, AttributeId, Attributes, MatchKind, Metadata, MetadataCollection, Outcome, TrackedAssignment,
9+
Value,
1110
},
12-
Assignment, NameRef, State,
11+
AssignmentRef, NameRef, StateRef,
1312
};
1413

1514
/// Initialization
16-
impl<'pattern> Outcome<'pattern> {
15+
impl Outcome {
1716
/// Initialize this instance to collect outcomes for all names in `collection`, which represents all possible attributes
18-
/// or macros we may visit.
17+
/// or macros we may visit, and [`reset`][Self::reset()] it unconditionally.
1918
///
2019
/// This must be called after each time `collection` changes.
2120
pub fn initialize(&mut self, collection: &MetadataCollection) {
@@ -74,7 +73,7 @@ impl<'pattern> Outcome<'pattern> {
7473
}
7574

7675
/// Access
77-
impl<'pattern> Outcome<'pattern> {
76+
impl Outcome {
7877
/// Return an iterator over all filled attributes we were initialized with.
7978
///
8079
/// ### Note
@@ -88,56 +87,63 @@ impl<'pattern> Outcome<'pattern> {
8887
/// the same as what `git` provides.
8988
/// Ours is in order of declaration, whereas `git` seems to list macros first somehow. Since the values are the same, this
9089
/// shouldn't be an issue.
91-
pub fn iter<'a>(&'a self) -> impl Iterator<Item = &'a Match<'pattern>> + 'a {
92-
self.matches_by_id.iter().filter_map(|item| item.r#match.as_ref())
90+
pub fn iter(&self) -> impl Iterator<Item = crate::search::Match<'_>> {
91+
self.matches_by_id
92+
.iter()
93+
.filter_map(|item| item.r#match.as_ref().map(|m| m.to_outer(self)))
9394
}
9495

9596
/// Iterate over all matches of the attribute selection in their original order.
96-
pub fn iter_selected<'a>(&'a self) -> impl Iterator<Item = Cow<'a, Match<'pattern>>> + 'a {
97+
///
98+
/// This only yields values if this instance was initialized with [`Outcome::initialize_with_selection()`].
99+
pub fn iter_selected(&self) -> impl Iterator<Item = crate::search::Match<'_>> {
97100
static DUMMY: Pattern = Pattern {
98101
text: BString::new(Vec::new()),
99102
mode: gix_glob::pattern::Mode::empty(),
100103
first_wildcard_pos: None,
101104
};
102105
self.selected.iter().map(|(name, id)| {
103-
id.and_then(|id| self.matches_by_id[id.0].r#match.as_ref())
104-
.map(Cow::Borrowed)
105-
.unwrap_or_else(|| {
106-
Cow::Owned(Match {
107-
pattern: &DUMMY,
108-
assignment: Assignment {
109-
name: NameRef::try_from(name.as_bytes().as_bstr())
110-
.unwrap_or_else(|_| NameRef("invalid".into()))
111-
.to_owned(),
112-
state: State::Unspecified,
113-
},
114-
kind: MatchKind::Attribute { macro_id: None },
115-
location: MatchLocation {
116-
source: None,
117-
sequence_number: 0,
118-
},
119-
})
106+
id.and_then(|id| self.matches_by_id[id.0].r#match.as_ref().map(|m| m.to_outer(self)))
107+
.unwrap_or_else(|| crate::search::Match {
108+
pattern: &DUMMY,
109+
assignment: AssignmentRef {
110+
name: NameRef::try_from(name.as_bytes().as_bstr())
111+
.unwrap_or_else(|_| NameRef("invalid".into())),
112+
state: StateRef::Unspecified,
113+
},
114+
kind: MatchKind::Attribute { macro_id: None },
115+
location: crate::search::MatchLocation {
116+
source: None,
117+
sequence_number: 0,
118+
},
120119
})
121120
})
122121
}
123122

124123
/// Obtain a match by the order of its attribute, if the order exists in our initialized attribute list and there was a match.
125-
pub fn match_by_id(&self, id: AttributeId) -> Option<&Match<'pattern>> {
126-
self.matches_by_id.get(id.0).and_then(|m| m.r#match.as_ref())
124+
pub fn match_by_id(&self, id: AttributeId) -> Option<crate::search::Match<'_>> {
125+
self.matches_by_id
126+
.get(id.0)
127+
.and_then(|m| m.r#match.as_ref().map(|m| m.to_outer(self)))
128+
}
129+
130+
/// Return `true` if there is nothing more to be done as all attributes were filled.
131+
pub fn is_done(&self) -> bool {
132+
self.remaining() == 0
127133
}
128134
}
129135

130136
/// Mutation
131-
impl<'pattern> Outcome<'pattern> {
137+
impl Outcome {
132138
/// Fill all `attrs` and resolve them recursively if they are macros. Return `true` if there is no attribute left to be resolved and
133139
/// we are totally done.
134140
/// `pattern` is what matched a patch and is passed for contextual information,
135141
/// providing `sequence_number` and `source` as well.
136142
pub(crate) fn fill_attributes<'a>(
137143
&mut self,
138144
attrs: impl Iterator<Item = &'a TrackedAssignment>,
139-
pattern: &'pattern gix_glob::Pattern,
140-
source: Option<&'pattern Path>,
145+
pattern: &gix_glob::Pattern,
146+
source: Option<&std::path::PathBuf>,
141147
sequence_number: usize,
142148
) -> bool {
143149
self.attrs_stack.extend(attrs.filter_map(|attr| {
@@ -155,8 +161,8 @@ impl<'pattern> Outcome<'pattern> {
155161
let is_macro = !slot.macro_attributes.is_empty();
156162

157163
slot.r#match = Some(Match {
158-
pattern,
159-
assignment: assignment.to_owned(),
164+
pattern: self.patterns.insert(pattern),
165+
assignment: self.assignments.insert_owned(assignment),
160166
kind: if is_macro {
161167
MatchKind::Macro {
162168
parent_macro_id: parent_order,
@@ -165,7 +171,7 @@ impl<'pattern> Outcome<'pattern> {
165171
MatchKind::Attribute { macro_id: parent_order }
166172
},
167173
location: MatchLocation {
168-
source,
174+
source: source.map(|path| self.source_paths.insert(path)),
169175
sequence_number,
170176
},
171177
});
@@ -188,7 +194,7 @@ impl<'pattern> Outcome<'pattern> {
188194
}
189195
}
190196

191-
impl<'attr> Outcome<'attr> {
197+
impl Outcome {
192198
/// Given a list of `attrs` by order, return true if at least one of them is not set
193199
pub(crate) fn has_unspecified_attributes(&self, mut attrs: impl Iterator<Item = AttributeId>) -> bool {
194200
attrs.any(|order| self.matches_by_id[order.0].r#match.is_none())
@@ -201,11 +207,6 @@ impl<'attr> Outcome<'attr> {
201207
.expect("BUG: instance must be initialized for each search set")
202208
}
203209

204-
/// Return true if there is nothing more to be done as all attributes were filled.
205-
pub(crate) fn is_done(&self) -> bool {
206-
self.remaining() == 0
207-
}
208-
209210
fn reduce_and_check_if_done(&mut self, attr: AttributeId) -> bool {
210211
if self.selected.is_empty()
211212
|| self
@@ -314,3 +315,51 @@ impl MatchKind {
314315
}
315316
}
316317
}
318+
319+
/// A version of `Match` without references.
320+
#[derive(Clone, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)]
321+
pub struct Match {
322+
/// The glob pattern itself, like `/target/*`.
323+
pub pattern: RefMapKey,
324+
/// The key=value pair of the attribute that matched at the pattern. There can be multiple matches per pattern.
325+
pub assignment: RefMapKey,
326+
/// Additional information about the kind of match.
327+
pub kind: MatchKind,
328+
/// Information about the location of the match.
329+
pub location: MatchLocation,
330+
}
331+
332+
impl Match {
333+
fn to_outer<'a>(&self, out: &'a Outcome) -> crate::search::Match<'a> {
334+
crate::search::Match {
335+
pattern: out.patterns.resolve(self.pattern).expect("pattern still present"),
336+
assignment: out
337+
.assignments
338+
.resolve(self.assignment)
339+
.expect("assignment present")
340+
.as_ref(),
341+
kind: self.kind,
342+
location: self.location.to_outer(out),
343+
}
344+
}
345+
}
346+
347+
/// A version of `MatchLocation` without references.
348+
#[derive(Clone, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)]
349+
pub struct MatchLocation {
350+
/// The path to the source from which the pattern was loaded, or `None` if it was specified by other means.
351+
pub source: Option<RefMapKey>,
352+
/// The line at which the pattern was found in its `source` file, or the occurrence in which it was provided.
353+
pub sequence_number: usize,
354+
}
355+
356+
impl MatchLocation {
357+
fn to_outer<'a>(&self, out: &'a Outcome) -> crate::search::MatchLocation<'a> {
358+
crate::search::MatchLocation {
359+
source: self
360+
.source
361+
.and_then(|source| out.source_paths.resolve(source).map(|p| p.as_path())),
362+
sequence_number: self.sequence_number,
363+
}
364+
}
365+
}

0 commit comments

Comments
 (0)