diff --git a/lib/src/local_working_copy.rs b/lib/src/local_working_copy.rs
index da6a2fb38e..177176cd74 100644
--- a/lib/src/local_working_copy.rs
+++ b/lib/src/local_working_copy.rs
@@ -16,6 +16,7 @@
#![allow(clippy::let_unit_value)]
use std::any::Any;
+use std::cmp::Ordering;
use std::collections::HashSet;
use std::error::Error;
use std::fs;
@@ -272,6 +273,13 @@ impl<'a> FileStates<'a> {
Self::from_sorted(&self.data[range])
}
+ /// Faster version of `prefixed("
/")`. Requires that all entries
+ /// share the same prefix `dir`.
+ fn prefixed_at(&self, dir: &RepoPath, base: &RepoPathComponent) -> Self {
+ let range = self.prefixed_range_at(dir, base);
+ Self::from_sorted(&self.data[range])
+ }
+
/// Returns true if this contains no entries.
pub fn is_empty(&self) -> bool {
self.data.is_empty()
@@ -289,12 +297,36 @@ impl<'a> FileStates<'a> {
Some(state)
}
+ /// Faster version of `get("/")`. Requires that all entries share
+ /// the same prefix `dir`.
+ fn get_at(&self, dir: &RepoPath, name: &RepoPathComponent) -> Option {
+ let pos = self.exact_position_at(dir, name)?;
+ let (_, state) = file_state_entry_from_proto(&self.data[pos]);
+ Some(state)
+ }
+
fn exact_position(&self, path: &RepoPath) -> Option {
self.data
.binary_search_by(|entry| RepoPath::from_internal_string(&entry.path).cmp(path))
.ok()
}
+ fn exact_position_at(&self, dir: &RepoPath, name: &RepoPathComponent) -> Option {
+ debug_assert!(self.paths().all(|path| path.starts_with(dir)));
+ let slash_len = !dir.is_root() as usize;
+ let prefix_len = dir.as_internal_file_string().len() + slash_len;
+ self.data
+ .binary_search_by(|entry| {
+ let tail = entry.path.get(prefix_len..).unwrap_or("");
+ match tail.split_once('/') {
+ // "/*" > ""
+ Some((pre, _)) => pre.cmp(name.as_internal_str()).then(Ordering::Greater),
+ None => tail.cmp(name.as_internal_str()),
+ }
+ })
+ .ok()
+ }
+
fn prefixed_range(&self, base: &RepoPath) -> Range {
let start = self
.data
@@ -304,6 +336,23 @@ impl<'a> FileStates<'a> {
start..(start + len)
}
+ fn prefixed_range_at(&self, dir: &RepoPath, base: &RepoPathComponent) -> Range {
+ debug_assert!(self.paths().all(|path| path.starts_with(dir)));
+ let slash_len = !dir.is_root() as usize;
+ let prefix_len = dir.as_internal_file_string().len() + slash_len;
+ let start = self.data.partition_point(|entry| {
+ let tail = entry.path.get(prefix_len..).unwrap_or("");
+ let entry_name = tail.split_once('/').map_or(tail, |(name, _)| name);
+ entry_name < base.as_internal_str()
+ });
+ let len = self.data[start..].partition_point(|entry| {
+ let tail = entry.path.get(prefix_len..).unwrap_or("");
+ let entry_name = tail.split_once('/').map_or(tail, |(name, _)| name);
+ entry_name == base.as_internal_str()
+ });
+ start..(start + len)
+ }
+
/// Iterates file state entries sorted by path.
pub fn iter(&self) -> FileStatesIter<'a> {
self.data.iter().map(file_state_entry_from_proto)
@@ -1122,15 +1171,16 @@ impl FileSnapshotter<'_> {
) -> Result