1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
use std::{collections::HashSet, ops::Deref};
use gix_object::Exists;
use crate::store::{load_index, Handle};
///
#[allow(clippy::empty_docs)]
pub mod lookup {
use crate::loose;
/// Returned by [`Handle::lookup_prefix()`][crate::store::Handle::lookup_prefix()]
#[derive(thiserror::Error, Debug)]
#[allow(missing_docs)]
pub enum Error {
#[error("An error occurred looking up a prefix which requires iteration")]
LooseWalkDir(#[from] loose::iter::Error),
#[error(transparent)]
LoadIndex(#[from] crate::store::load_index::Error),
}
/// A way to indicate if a lookup, despite successful, was ambiguous or yielded exactly
/// one result in the particular index.
pub type Outcome = Result<gix_hash::ObjectId, ()>;
}
///
#[allow(clippy::empty_docs)]
pub mod disambiguate {
/// A potentially ambiguous prefix for use with `Handle::disambiguate_prefix()`.
#[derive(Debug, Copy, Clone)]
pub struct Candidate {
id: gix_hash::ObjectId,
hex_len: usize,
}
impl Candidate {
/// Create a new potentially ambiguous prefix from an `id` and the desired minimal `hex_len`.
///
/// It is considered ambiguous until it's disambiguated by validating that there is only a single object
/// matching this prefix.
pub fn new(id: impl Into<gix_hash::ObjectId>, hex_len: usize) -> Result<Self, gix_hash::prefix::Error> {
let id = id.into();
gix_hash::Prefix::new(&id, hex_len)?;
Ok(Candidate { id, hex_len })
}
/// Transform ourselves into a `Prefix` with our current hex lengths.
pub fn to_prefix(&self) -> gix_hash::Prefix {
gix_hash::Prefix::new(&self.id, self.hex_len).expect("our hex-len to always be in bounds")
}
pub(crate) fn inc_hex_len(&mut self) {
self.hex_len += 1;
assert!(self.hex_len <= self.id.kind().len_in_hex());
}
pub(crate) fn id(&self) -> &gix_hash::oid {
&self.id
}
pub(crate) fn hex_len(&self) -> usize {
self.hex_len
}
}
/// Returned by [`Handle::disambiguate_prefix()`][crate::store::Handle::disambiguate_prefix()]
#[derive(thiserror::Error, Debug)]
#[allow(missing_docs)]
pub enum Error {
#[error("An error occurred while trying to determine if a full hash contained in the object database")]
Contains(#[from] crate::store::find::Error),
#[error(transparent)]
Lookup(#[from] super::lookup::Error),
}
}
impl<S> Handle<S>
where
S: Deref<Target = super::Store> + Clone,
{
/// Return the exact number of packed objects after loading all currently available indices
/// as last seen on disk.
pub fn packed_object_count(&self) -> Result<u64, load_index::Error> {
let mut count = self.packed_object_count.borrow_mut();
match *count {
Some(count) => Ok(count),
None => {
let _span = gix_features::trace::detail!("gix_odb::Handle::packed_object_count()");
let mut snapshot = self.snapshot.borrow_mut();
*snapshot = self.store.load_all_indices()?;
let mut obj_count = 0;
for index in &snapshot.indices {
obj_count += index.num_objects() as u64;
}
*count = Some(obj_count);
Ok(obj_count)
}
}
}
/// Given a prefix `candidate` with an object id and an initial `hex_len`, check if it only matches a single
/// object within the entire object database and increment its `hex_len` by one until it is unambiguous.
/// Return `Ok(None)` if no object with that prefix exists.
pub fn disambiguate_prefix(
&self,
mut candidate: disambiguate::Candidate,
) -> Result<Option<gix_hash::Prefix>, disambiguate::Error> {
let max_hex_len = candidate.id().kind().len_in_hex();
if candidate.hex_len() == max_hex_len {
return Ok(self.exists(candidate.id()).then(|| candidate.to_prefix()));
}
while candidate.hex_len() != max_hex_len {
let res = self.lookup_prefix(candidate.to_prefix(), None)?;
match res {
Some(Ok(_id)) => return Ok(Some(candidate.to_prefix())),
Some(Err(())) => {
candidate.inc_hex_len();
continue;
}
None => return Ok(None),
}
}
Ok(Some(candidate.to_prefix()))
}
/// Find the only object matching `prefix` and return it as `Ok(Some(Ok(<ObjectId>)))`, or return `Ok(Some(Err(()))`
/// if multiple different objects with the same prefix were found.
///
/// Return `Ok(None)` if no object matched the `prefix`.
///
/// Pass `candidates` to obtain the set of all object ids matching `prefix`, with the same return value as
/// one would have received if it remained `None`.
///
/// ### Performance Note
///
/// - Unless the handles refresh mode is set to `Never`, each lookup will trigger a refresh of the object databases files
/// on disk if the prefix doesn't lead to ambiguous results.
/// - Since all objects need to be examined to assure non-ambiguous return values, after calling this method all indices will
/// be loaded.
/// - If `candidates` is `Some(…)`, the traversal will continue to obtain all candidates, which takes more time
/// as there is no early abort.
pub fn lookup_prefix(
&self,
prefix: gix_hash::Prefix,
mut candidates: Option<&mut HashSet<gix_hash::ObjectId>>,
) -> Result<Option<lookup::Outcome>, lookup::Error> {
let mut candidate: Option<gix_hash::ObjectId> = None;
loop {
let snapshot = self.snapshot.borrow();
for index in &snapshot.indices {
#[allow(clippy::needless_option_as_deref)] // needed as it's the equivalent of a reborrow.
let lookup_result = index.lookup_prefix(prefix, candidates.as_deref_mut());
if candidates.is_none() && !check_candidate(lookup_result, &mut candidate) {
return Ok(Some(Err(())));
}
}
for lodb in snapshot.loose_dbs.iter() {
#[allow(clippy::needless_option_as_deref)] // needed as it's the equivalent of a reborrow.
let lookup_result = lodb.lookup_prefix(prefix, candidates.as_deref_mut())?;
if candidates.is_none() && !check_candidate(lookup_result, &mut candidate) {
return Ok(Some(Err(())));
}
}
match self.store.load_one_index(self.refresh, snapshot.marker)? {
Some(new_snapshot) => {
drop(snapshot);
*self.snapshot.borrow_mut() = new_snapshot;
}
None => {
return match &candidates {
Some(candidates) => match candidates.len() {
0 => Ok(None),
1 => Ok(candidates.iter().copied().next().map(Ok)),
_ => Ok(Some(Err(()))),
},
None => Ok(candidate.map(Ok)),
};
}
}
}
fn check_candidate(lookup_result: Option<lookup::Outcome>, candidate: &mut Option<gix_hash::ObjectId>) -> bool {
match (lookup_result, &*candidate) {
(Some(Ok(oid)), Some(candidate)) if *candidate != oid => false,
(Some(Ok(_)) | None, Some(_)) | (None, None) => true,
(Some(Err(())), _) => false,
(Some(Ok(oid)), None) => {
*candidate = Some(oid);
true
}
}
}
}
}