diff --git a/eden/scm/lib/manifest-tree/src/store.rs b/eden/scm/lib/manifest-tree/src/store.rs index 4efcbf18b1..49eb82ccc1 100644 --- a/eden/scm/lib/manifest-tree/src/store.rs +++ b/eden/scm/lib/manifest-tree/src/store.rs @@ -5,6 +5,7 @@ * GNU General Public License version 2. */ +use std::cmp::Ordering; use std::str::from_utf8; use std::str::FromStr; use std::sync::Arc; @@ -108,7 +109,7 @@ pub struct Element { } /// Used to signal the type of element in a directory: file or directory. -#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub enum Flag { File(FileType), Directory, @@ -257,16 +258,117 @@ impl<'a> Elements<'a> { }; Some(Ok(element)) } + + /// Look up an item. + /// This can be faster than checking `next()` entries if only called once. + pub fn lookup(&self, name: &PathComponent) -> Result> { + match self.format { + TreeFormat::Hg => self.lookup_hg(name), + TreeFormat::Git => self.lookup_git(name), + } + } + + fn lookup_hg(&self, name: &PathComponent) -> Result> { + // NAME '\0' HEX_SHA1 MODE '\n' + let mut slice: &[u8] = self.byte_slice; + let name = { + let name = name.as_byte_slice(); + let mut buf = Vec::with_capacity(name.len() + 1); + buf.extend_from_slice(name); + buf.push(b'\0'); + buf + }; + while slice.len() >= name.len() { + match slice[..name.len()].cmp(name.as_slice()) { + // XXX: Some tests do not provide sorted entries. + Ordering::Less | Ordering::Greater => { + // Check the next entry. + match slice.iter().skip(HgId::hex_len()).position(|&x| x == b'\n') { + Some(position) => { + slice = &slice[position + HgId::hex_len() + 1..]; + continue; + } + None => break, + }; + } + Ordering::Equal => { + let hex_start = name.len(); + let hex_end = hex_start + HgId::hex_len(); + let hex_slice = match slice.get(hex_start..hex_end) { + None => break, + Some(slice) => slice, + }; + let hgid = HgId::from_hex(hex_slice)?; + let flag = parse_hg_flag(slice.get(hex_end))?; + return Ok(Some((hgid, flag))); + } + } + } + Ok(None) + } + + fn lookup_git(&self, name: &PathComponent) -> Result> { + let mut slice: &[u8] = self.byte_slice; + let name: &[u8] = name.as_byte_slice(); + while !slice.is_empty() { + let (mode_len, name_len) = match find_git_entry_positions(slice) { + Some(positions) => positions, + None => return Ok(None), + }; + let name_start = mode_len + 1; + let name_end = name_start + name_len; + let candidate = match slice.get(name_start..name_end) { + Some(name) => name, + None => break, + }; + match candidate.cmp(name) { + Ordering::Less => { + slice = match slice.get(name_end + 1 + HgId::len()..) { + Some(slice) => slice, + None => break, + }; + continue; + } + Ordering::Equal => { + let flag = parse_git_mode(&slice[..mode_len])?; + let id_start = name_end + 1; + let id_end = id_start + HgId::len(); + let hgid = if let Some(id_slice) = slice.get(id_start..id_end) { + HgId::from_slice(id_slice).expect("id_slice has the right length") + } else { + return Err(format_err!("SHA1 is incomplete")); + }; + return Ok(Some((hgid, flag))); + } + Ordering::Greater => break, + } + } + Ok(None) + } } impl<'a> Iterator for Elements<'a> { type Item = Result; fn next(&mut self) -> Option { - match self.format { + let item = match self.format { TreeFormat::Hg => self.next_hg(), TreeFormat::Git => self.next_git(), + }; + + if cfg!(debug_assertions) { + if let Some(Ok(item)) = &item { + let lookup = self.lookup(&item.component).unwrap(); + assert_eq!( + Some((item.hgid, item.flag)), + lookup, + "when lookup '{}'", + item.component.as_str() + ); + } } + + item } }