Skip to content

Commit 05ed965

Browse files
committed
feat: add State::prefixed_entries() returning a range of entries with a given prefix.
This is useful to limit entry traversal and thus do less work.
1 parent b1e55d6 commit 05ed965

File tree

2 files changed

+66
-1
lines changed

2 files changed

+66
-1
lines changed

gix-index/src/access/mod.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,34 @@ impl State {
158158
Some(&self.entries[idx])
159159
}
160160

161+
/// Return the slice of entries which all share the same `prefix`, or `None` if there isn't a single such entry.
162+
pub fn prefixed_entries(&self, prefix: &BStr) -> Option<&[Entry]> {
163+
if prefix.is_empty() {
164+
return Some(self.entries());
165+
}
166+
let prefix_len = prefix.len();
167+
let mut low = self
168+
.entries
169+
.partition_point(|e| e.path(self).get(..prefix_len).map_or(true, |p| p < prefix));
170+
let mut high = low
171+
+ self.entries[low..].partition_point(|e| e.path(self).get(..prefix_len).map_or(false, |p| p <= prefix));
172+
173+
let low_entry = &self.entries[low];
174+
if low_entry.stage() != 0 {
175+
low = self
176+
.entry_index_by_idx_and_stage(low_entry.path(self), low, 0, low_entry.stage().cmp(&0))
177+
.unwrap_or(low);
178+
}
179+
if let Some(high_entry) = self.entries.get(high) {
180+
if low_entry.stage() != 2 {
181+
high = self
182+
.entry_index_by_idx_and_stage(high_entry.path(self), high, 2, high_entry.stage().cmp(&2))
183+
.unwrap_or(high);
184+
}
185+
}
186+
(low != high).then_some(low..high).map(|range| &self.entries[range])
187+
}
188+
161189
/// Return the entry at `idx` or _panic_ if the index is out of bounds.
162190
///
163191
/// The `idx` is typically returned by [`entry_by_path_and_stage()`][State::entry_by_path_and_stage()].

gix-index/tests/index/access.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,17 @@ fn entry_by_path_with_conflicting_file() {
2828
file.entry_by_path("file".into()).expect("found").stage(),
2929
2,
3030
"we always find our stage while in a merge"
31-
)
31+
);
32+
assert_eq!(
33+
file.prefixed_entries("fil".into()).expect("present"),
34+
file.entries(),
35+
"it's possible to get the entire range"
36+
);
37+
assert_eq!(
38+
file.prefixed_entries("".into()).expect("present"),
39+
file.entries(),
40+
"empty prefix matches all"
41+
);
3242
}
3343

3444
#[test]
@@ -65,4 +75,31 @@ fn sort_entries() {
6575
new_entry_path,
6676
"we can find the correct entry now"
6777
);
78+
79+
check_prefix(&file, "a", &["a", "an initially incorrectly ordered entry"]);
80+
check_prefix(
81+
&file,
82+
"d",
83+
&["d/a", "d/b", "d/c", "d/last/123", "d/last/34", "d/last/6"],
84+
);
85+
check_prefix(
86+
&file,
87+
"d/",
88+
&["d/a", "d/b", "d/c", "d/last/123", "d/last/34", "d/last/6"],
89+
);
90+
check_prefix(&file, "d/last", &["d/last/123", "d/last/34", "d/last/6"]);
91+
check_prefix(&file, "d/las", &["d/last/123", "d/last/34", "d/last/6"]);
92+
check_prefix(&file, "x", &["x"]);
93+
}
94+
95+
fn check_prefix(index: &gix_index::State, prefix: &str, expected: &[&str]) {
96+
assert_eq!(
97+
index
98+
.prefixed_entries(prefix.into())
99+
.expect("present")
100+
.iter()
101+
.map(|e| e.path(index))
102+
.collect::<Vec<_>>(),
103+
expected
104+
);
68105
}

0 commit comments

Comments
 (0)