270 lines · 10.9 KB · Rust
RawDownload
pub use error::Error; mod error; pub(crate) struct TreeEntry {    pub id: gix_hash::ObjectId,    pub crc32: u32,} /// Information gathered while executing [`write_data_iter_to_stream()`][crate::index::write_data_iter_to_stream]#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]pub struct Outcome {    /// The version of the verified index    pub index_version: crate::index::Version,    /// The verified checksum of the verified index    pub index_hash: gix_hash::ObjectId,     /// The hash of the '.pack' file, also found in its trailing bytes    pub data_hash: gix_hash::ObjectId,    /// The amount of objects that were verified, always the amount of objects in the pack.    pub num_objects: u32,} /// The progress ids used in [`write_data_iter_to_stream()`][crate::index::write_data_iter_to_stream()].////// Use this information to selectively extract the progress of interest in case the parent application has custom visualization.#[derive(Debug, Copy, Clone)]pub enum ProgressId {    /// Counts the amount of objects that were index thus far.    IndexObjects,    /// The amount of bytes that were decompressed while decoding pack entries.    ///    /// This is done to determine entry boundaries.    DecompressedBytes,    /// The amount of objects whose hashes were computed.    ///    /// This is done by decoding them, which typically involves decoding delta objects.    ResolveObjects,    /// The amount of bytes that were decoded in total, as the sum of all bytes to represent all resolved objects.    DecodedBytes,    /// The amount of bytes written to the index file.    IndexBytesWritten,} impl From<ProgressId> for gix_features::progress::Id {    fn from(v: ProgressId) -> Self {        match v {            ProgressId::IndexObjects => *b"IWIO",            ProgressId::DecompressedBytes => *b"IWDB",            ProgressId::ResolveObjects => *b"IWRO",            ProgressId::DecodedBytes => *b"IWDB",            ProgressId::IndexBytesWritten => *b"IWBW",        }    }} pub(super) mod function {    use std::{io, sync::atomic::AtomicBool};     use gix_features::progress::{self, Count, Progress, prodash::DynNestedProgress};     use crate::cache::delta::{Tree, traverse};     use super::{Error, Outcome, ProgressId, TreeEntry, modify_base};     /// Write information about `entries` as obtained from a pack data file into a pack index file via the `out` stream.    /// The resolver produced by `make_resolver` must resolve pack entries from the same pack data file that produced the    /// `entries` iterator.    ///    /// * `kind` is the version of pack index to produce, use [`crate::index::Version::default()`] if in doubt.    /// * `tread_limit` is used for a parallel tree traversal for obtaining object hashes with optimal performance.    /// * `root_progress` is the top-level progress to stay informed about the progress of this potentially long-running    ///   computation.    /// * `object_hash` defines what kind of object hash we write into the index file.    /// * `alloc_limit_bytes` limits the maximum size of individual allocations while resolving pack entries to compute    ///   object ids. `None` means no limit is applied.    /// * `pack_version` is the version of the underlying pack for which `entries` are read. It's used in case none of these objects are provided    ///   to compute a pack-hash.    ///    /// # Remarks    ///    /// * neither in-pack nor out-of-pack Ref Deltas are supported here, these must have been resolved beforehand.    /// * `make_resolver()` will only be called after the iterator stopped returning elements and produces a function that    ///   provides all bytes belonging to a pack entry writing them to the given mutable output `Vec`.    ///   It should return `None` if the entry cannot be resolved from the pack that produced the `entries` iterator, causing    ///   the write operation to fail.    #[expect(clippy::too_many_arguments)]    pub fn write_data_iter_to_stream<F, F2, R>(        version: crate::index::Version,        make_resolver: F,        entries: &mut dyn Iterator<Item = Result<crate::data::input::Entry, crate::data::input::Error>>,        thread_limit: Option<usize>,        root_progress: &mut dyn DynNestedProgress,        out: &mut dyn io::Write,        should_interrupt: &AtomicBool,        object_hash: gix_hash::Kind,        alloc_limit_bytes: Option<usize>,        pack_version: crate::data::Version,    ) -> Result<Outcome, Error>    where        F: FnOnce() -> io::Result<(F2, R)>,        R: Send + Sync,        F2: for<'r> Fn(crate::data::EntryRange, &'r R) -> Option<&'r [u8]> + Send + Clone,    {        if version != crate::index::Version::default() {            return Err(Error::Unsupported(version));        }        let mut num_objects: usize = 0;        let mut last_seen_trailer = None;        let (anticipated_num_objects, upper_bound) = entries.size_hint();        let worst_case_num_objects_after_thin_pack_resolution = upper_bound.unwrap_or(anticipated_num_objects);        let mut tree = Tree::with_capacity(worst_case_num_objects_after_thin_pack_resolution)?;        let indexing_start = std::time::Instant::now();         root_progress.init(Some(4), progress::steps());        let mut objects_progress = root_progress.add_child_with_id("indexing".into(), ProgressId::IndexObjects.into());        objects_progress.init(Some(anticipated_num_objects), progress::count("objects"));        let mut decompressed_progress =            root_progress.add_child_with_id("decompressing".into(), ProgressId::DecompressedBytes.into());        decompressed_progress.init(None, progress::bytes());        let mut pack_entries_end: u64 = 0;         for entry in entries {            let crate::data::input::Entry {                header,                pack_offset,                crc32,                header_size,                compressed: _,                compressed_size,                decompressed_size,                trailer,            } = entry?;             decompressed_progress.inc_by(decompressed_size as usize);             let entry_len = u64::from(header_size) + compressed_size;            pack_entries_end = pack_offset + entry_len;             let crc32 = crc32.expect("crc32 to be computed by the iterator. Caller assures correct configuration.");             use crate::data::entry::Header::*;            match header {                Tree | Blob | Commit | Tag => {                    tree.add_root(                        pack_offset,                        TreeEntry {                            id: object_hash.null(),                            crc32,                        },                    )?;                }                RefDelta { .. } => return Err(Error::IteratorInvariantNoRefDelta),                OfsDelta { base_distance } => {                    let base_pack_offset =                        crate::data::entry::Header::verified_base_pack_offset(pack_offset, base_distance).ok_or(                            Error::IteratorInvariantBaseOffset {                                pack_offset,                                distance: base_distance,                            },                        )?;                    tree.add_child(                        base_pack_offset,                        pack_offset,                        TreeEntry {                            id: object_hash.null(),                            crc32,                        },                    )?;                }            }            last_seen_trailer = trailer;            num_objects += 1;            objects_progress.inc();        }        let num_objects: u32 = num_objects            .try_into()            .map_err(|_| Error::IteratorInvariantTooManyObjects(num_objects))?;         objects_progress.show_throughput(indexing_start);        decompressed_progress.show_throughput(indexing_start);        drop(objects_progress);        drop(decompressed_progress);         root_progress.inc();         let (resolver, pack) = make_resolver().map_err(gix_hash::io::Error::from)?;        let sorted_pack_offsets_by_oid = {            let traverse::Outcome { roots, children } = tree.traverse(                resolver,                &pack,                pack_entries_end,                |data,                 _progress,                 traverse::Context {                     entry,                     decompressed: bytes,                     ..                 }| { modify_base(data, entry, bytes, object_hash) },                traverse::Options {                    object_progress: Box::new(                        root_progress.add_child_with_id("Resolving".into(), ProgressId::ResolveObjects.into()),                    ),                    size_progress: &mut root_progress                        .add_child_with_id("Decoding".into(), ProgressId::DecodedBytes.into()),                    thread_limit,                    should_interrupt,                    object_hash,                    alloc_limit_bytes,                },            )?;            root_progress.inc();             let mut items = roots;            items.extend(children);            {                let _progress =                    root_progress.add_child_with_id("sorting by id".into(), gix_features::progress::UNKNOWN);                items.sort_by_key(|e| e.data.id);            }             root_progress.inc();            items        };         let pack_hash = match last_seen_trailer {            Some(ph) => ph,            None if num_objects == 0 => {                let header = crate::data::header::encode(pack_version, 0);                let mut hasher = gix_hash::hasher(object_hash);                hasher.update(&header);                hasher.try_finalize().map_err(gix_hash::io::Error::from)?            }            None => return Err(Error::IteratorInvariantTrailer),        };        let index_hash = crate::index::encode::write_to(            out,            sorted_pack_offsets_by_oid,            &pack_hash,            version,            object_hash,            &mut root_progress.add_child_with_id("writing index file".into(), ProgressId::IndexBytesWritten.into()),        )?;        root_progress.show_throughput_with(            indexing_start,            num_objects as usize,            progress::count("objects").expect("unit always set"),            progress::MessageLevel::Success,        );        Ok(Outcome {            index_version: version,            index_hash,            data_hash: pack_hash,            num_objects,        })    }} fn modify_base(    entry: &mut TreeEntry,    pack_entry: &crate::data::Entry,    decompressed: &[u8],    hash: gix_hash::Kind,) -> Result<(), gix_hash::hasher::Error> {    let object_kind = pack_entry.header.as_kind().expect("base object as source of iteration");    let id = gix_object::compute_hash(hash, object_kind, decompressed)?;    entry.id = id;    Ok(())}