diff --git a/lib/src/local_working_copy.rs b/lib/src/local_working_copy.rs
index acf38f18e5..b47b374893 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])
}
+ /// Suppose all entries share the same prefix `dir`, returns file states
+ /// under the `
/` path.
+ fn prefixed_at(&self, dir: &RepoPath, name: &RepoPathComponent) -> Self {
+ let range = self.prefixed_range_at(dir, name);
+ 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,35 @@ impl<'a> FileStates<'a> {
Some(state)
}
+ /// Suppose all entries share the same prefix `dir`, returns file state for
+ /// the `/` path.
+ 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 prefix_len = dir.as_internal_file_string().len() + (!dir.is_root() as usize);
+ 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 +335,24 @@ impl<'a> FileStates<'a> {
start..(start + len)
}
+ fn prefixed_range_at(&self, dir: &RepoPath, name: &RepoPathComponent) -> Range {
+ debug_assert!(self.paths().all(|path| path.starts_with(dir)));
+ fn coerce &str>(f: F) -> F {
+ f
+ }
+ let prefix_len = dir.as_internal_file_string().len() + (!dir.is_root() as usize);
+ let to_name = coerce(|path| {
+ let tail = path.get(prefix_len..).unwrap_or("");
+ tail.split_once('/').map_or(tail, |(name, _)| name)
+ });
+ let start = self
+ .data
+ .partition_point(|entry| to_name(&entry.path) < name.as_internal_str());
+ let len = self.data[start..]
+ .partition_point(|entry| to_name(&entry.path) == name.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)
@@ -1115,15 +1164,16 @@ impl FileSnapshotter<'_> {
) -> Result