diff --git a/git-hash/src/owned.rs b/git-hash/src/owned.rs index 0aa711d8c3b..a6444a1371d 100644 --- a/git-hash/src/owned.rs +++ b/git-hash/src/owned.rs @@ -71,6 +71,23 @@ impl Prefix { pub fn hex_len(&self) -> usize { self.hex_len } + + /// Provided with candidate id which is a full hash, determine if this prefix fully matches + /// the corresponding part in the candidate id. + pub fn matches_candidate(&self, candidate: &oid) -> bool { + let common_len = self.hex_len / 2; + + if !(self.bytes.as_bytes()[..common_len] == candidate.as_bytes()[..common_len]) { + return false; + } + + if self.hex_len % 2 == 1 { + let half_byte_idx = self.hex_len / 2; + self.bytes.as_bytes()[half_byte_idx] == (candidate.as_bytes()[half_byte_idx] & 0xf0) + } else { + true + } + } } /// An owned hash identifying objects, most commonly Sha1 diff --git a/git-pack/src/multi_index/access.rs b/git-pack/src/multi_index/access.rs index 6f2386f5b7f..8b4ac6d8330 100644 --- a/git-pack/src/multi_index/access.rs +++ b/git-pack/src/multi_index/access.rs @@ -68,8 +68,32 @@ impl File { } /// TODO - pub fn lookup_prefix(&self, _prefix: git_hash::Prefix) -> Option { - todo!() + pub fn lookup_prefix(&self, prefix: git_hash::Prefix) -> Option { + let prefix_oid = prefix.as_oid(); + let first_byte = prefix_oid.first_byte() as usize; + let mut upper_bound = self.fan[first_byte]; + let mut lower_bound = if first_byte != 0 { self.fan[first_byte - 1] } else { 0 }; + + // Bisect using indices + let mut candidate = None; + while lower_bound < upper_bound { + let mid = (lower_bound + upper_bound) / 2; + let mid_sha = self.oid_at_index(mid); + candidate = Some(mid); + + use std::cmp::Ordering::*; + match prefix_oid.cmp(mid_sha) { + Less => upper_bound = mid, + Equal if prefix.hex_len() == prefix_oid.kind().len_in_hex() => return Some(Ok(mid)), + Equal => break, + Greater => lower_bound = mid + 1, + } + } + + let candidate = candidate?; + prefix + .matches_candidate(self.oid_at_index(candidate)) + .then(|| Ok(candidate)) } /// Find the index ranging from 0 to [File::num_objects()] that belongs to data associated with `id`, or `None` if it wasn't found.