use gix_error::Result;
use std::sync::atomic::{AtomicBool, Ordering};
use gix_error::bail;
use gix_parallel as threading;
use gix_parallel::{Mutable, OwnShared};
use gix_utils::progress::{self, DynNestedProgress, Progress};
use crate::{
cache::delta::{Tree, traverse::util::ItemSliceSync, tree::Item},
data::EntryRange,
};
mod resolve;
pub(crate) mod util;
pub(super) type SharedRefDeltaChildren = OwnShared<Mutable<super::tree::RefDeltaChildren>>;
#[cold]
pub(super) fn allocation_error(kind: gix_error::ResourceExhaustionKind) -> gix_error::Message {
gix_error::resource_exhaustion(kind, "Entry too large to fit in memory")
}
pub(super) fn interrupted() -> gix_error::Error {
gix_error::message("Interrupted").cancelled_error()
}
pub struct Context<'a> {
pub entry: &'a crate::data::Entry,
pub entry_end: u64,
pub decompressed: &'a [u8],
pub level: u16,
}
pub struct Options<'a, 's> {
pub object_progress: Box<dyn DynNestedProgress>,
pub size_progress: &'s mut dyn Progress,
pub thread_limit: Option<usize>,
pub should_interrupt: &'a AtomicBool,
pub object_hash: gix_hash::Kind,
pub alloc_limit_bytes: Option<usize>,
}
pub struct Outcome<T> {
pub roots: Vec<Item<T>>,
pub children: Vec<Item<T>>,
}
impl<T> Tree<T>
where
T: Send,
{
pub fn traverse<F, MBFN, R>(
mut self,
resolve: F,
resolve_data: &R,
pack_entries_end: u64,
inspect_object: MBFN,
Options {
thread_limit,
mut object_progress,
size_progress,
should_interrupt,
object_hash,
alloc_limit_bytes,
}: Options<'_, '_>,
) -> Result<Outcome<T>>
where
F: for<'r> Fn(EntryRange, &'r R) -> Option<&'r [u8]> + Send + Clone,
R: Send + Sync,
MBFN: FnMut(&mut T, &dyn Progress, Context<'_>) -> Result + Send + Clone,
{
self.set_pack_entries_end_and_resolve_ref_offsets(pack_entries_end)?;
let num_objects = self.num_items();
let object_counter = {
let progress = &mut object_progress;
progress.init(Some(num_objects), progress::count("objects"));
progress.set(0);
progress.counter()
};
size_progress.init(None, progress::bytes());
let size_counter = size_progress.counter();
let resolver_progress = object_progress.add_child("delta resolver".into());
let start = std::time::Instant::now();
let (mut root_items, mut child_items_vec, ref_delta_children) = self.take_root_child_and_refs();
let ref_delta_children =
(!ref_delta_children.is_empty()).then(|| OwnShared::new(Mutable::new(ref_delta_children)));
let child_items = ItemSliceSync::new(&mut child_items_vec);
#[expect(unsafe_code)]
unsafe {
resolve::all(
&mut root_items,
&child_items,
thread_limit,
num_objects,
object_counter.clone(),
size_counter,
&resolver_progress,
resolve,
resolve_data,
inspect_object,
ref_delta_children.clone(),
object_hash,
alloc_limit_bytes,
should_interrupt,
)?;
}
if let Some(ref_delta_children) = ref_delta_children
&& let Some((base_id, _children)) = threading::lock(&ref_delta_children).first_key_value()
{
bail!("The ref-delta base object {base_id} could not be found".not_found());
}
if object_counter.load(Ordering::Relaxed) != num_objects {
bail!(gix_error::corruption("Pack delta traversal left unresolved objects"));
}
object_progress.show_throughput(start);
size_progress.show_throughput(start);
Ok(Outcome {
roots: root_items,
children: child_items_vec,
})
}
}