Skip to content

Commit

Permalink
feat: add State::prefixed_entries() returning a range of entries wi…
Browse files Browse the repository at this point in the history
…th a given prefix.

This is useful to limit entry traversal and thus do less work.
  • Loading branch information
Byron committed Aug 14, 2023
1 parent b1e55d6 commit 05ed965
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 1 deletion.
28 changes: 28 additions & 0 deletions gix-index/src/access/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,34 @@ impl State {
Some(&self.entries[idx])
}

/// Return the slice of entries which all share the same `prefix`, or `None` if there isn't a single such entry.
pub fn prefixed_entries(&self, prefix: &BStr) -> Option<&[Entry]> {
if prefix.is_empty() {
return Some(self.entries());
}
let prefix_len = prefix.len();
let mut low = self
.entries
.partition_point(|e| e.path(self).get(..prefix_len).map_or(true, |p| p < prefix));
let mut high = low
+ self.entries[low..].partition_point(|e| e.path(self).get(..prefix_len).map_or(false, |p| p <= prefix));

let low_entry = &self.entries[low];
if low_entry.stage() != 0 {
low = self
.entry_index_by_idx_and_stage(low_entry.path(self), low, 0, low_entry.stage().cmp(&0))
.unwrap_or(low);
}
if let Some(high_entry) = self.entries.get(high) {
if low_entry.stage() != 2 {
high = self
.entry_index_by_idx_and_stage(high_entry.path(self), high, 2, high_entry.stage().cmp(&2))
.unwrap_or(high);
}
}
(low != high).then_some(low..high).map(|range| &self.entries[range])
}

/// Return the entry at `idx` or _panic_ if the index is out of bounds.
///
/// The `idx` is typically returned by [`entry_by_path_and_stage()`][State::entry_by_path_and_stage()].
Expand Down
39 changes: 38 additions & 1 deletion gix-index/tests/index/access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,17 @@ fn entry_by_path_with_conflicting_file() {
file.entry_by_path("file".into()).expect("found").stage(),
2,
"we always find our stage while in a merge"
)
);
assert_eq!(
file.prefixed_entries("fil".into()).expect("present"),
file.entries(),
"it's possible to get the entire range"
);
assert_eq!(
file.prefixed_entries("".into()).expect("present"),
file.entries(),
"empty prefix matches all"
);
}

#[test]
Expand Down Expand Up @@ -65,4 +75,31 @@ fn sort_entries() {
new_entry_path,
"we can find the correct entry now"
);

check_prefix(&file, "a", &["a", "an initially incorrectly ordered entry"]);
check_prefix(
&file,
"d",
&["d/a", "d/b", "d/c", "d/last/123", "d/last/34", "d/last/6"],
);
check_prefix(
&file,
"d/",
&["d/a", "d/b", "d/c", "d/last/123", "d/last/34", "d/last/6"],
);
check_prefix(&file, "d/last", &["d/last/123", "d/last/34", "d/last/6"]);
check_prefix(&file, "d/las", &["d/last/123", "d/last/34", "d/last/6"]);
check_prefix(&file, "x", &["x"]);
}

fn check_prefix(index: &gix_index::State, prefix: &str, expected: &[&str]) {
assert_eq!(
index
.prefixed_entries(prefix.into())
.expect("present")
.iter()
.map(|e| e.path(index))
.collect::<Vec<_>>(),
expected
);
}

0 comments on commit 05ed965

Please sign in to comment.