200 lines · 7.8 KB · Rust
RawDownload
use std::{    collections::VecDeque,    sync::{        atomic::{AtomicBool, Ordering},        Arc,    },}; use git_features::{parallel, progress::Progress}; use crate::cache::delta::traverse::Context;use crate::index::{self, util::index_entries_sorted_by_offset_ascending}; use super::{Error, SafetyCheck}; /// Traversal with indeximpl index::File {    /// Iterate through all _decoded objects_ in the given `pack` and handle them with a `Processor`, using an index to reduce waste    /// at the cost of memory.    ///    /// For more details, see the documentation on the [`traverse()`][index::File::traverse()] method.    pub fn traverse_with_index<P, Processor, E>(        &self,        check: SafetyCheck,        thread_limit: Option<usize>,        new_processor: impl Fn() -> Processor + Send + Sync,        mut progress: P,        pack: &crate::data::File,        should_interrupt: Arc<AtomicBool>,    ) -> Result<(git_hash::ObjectId, index::traverse::Outcome, P), Error<E>>    where        P: Progress,        Processor: FnMut(            git_object::Kind,            &[u8],            &index::Entry,            &mut <<P as Progress>::SubProgress as Progress>::SubProgress,        ) -> Result<(), E>,        E: std::error::Error + Send + Sync + 'static,    {        let (verify_result, traversal_result) = parallel::join(            {                let pack_progress = progress.add_child("SHA1 of pack");                let index_progress = progress.add_child("SHA1 of index");                let should_interrupt = Arc::clone(&should_interrupt);                move || {                    let res = self.possibly_verify(                        pack,                        check,                        pack_progress,                        index_progress,                        Arc::clone(&should_interrupt),                    );                    if res.is_err() {                        should_interrupt.store(true, Ordering::SeqCst);                    }                    res                }            },            || -> Result<_, Error<_>> {                let sorted_entries =                    index_entries_sorted_by_offset_ascending(self, progress.add_child("collecting sorted index"));                let tree = crate::cache::delta::Tree::from_offsets_in_pack(                    sorted_entries.into_iter().map(EntryWithDefault::from),                    |e| e.index_entry.pack_offset,                    pack.path(),                    progress.add_child("indexing"),                    &should_interrupt,                    |id| self.lookup(id).map(|idx| self.pack_offset_at_index(idx)),                )?;                let there_are_enough_objects = || self.num_objects > 10_000;                let mut outcome = digest_statistics(tree.traverse(                    there_are_enough_objects,                    |slice, out| pack.entry_slice(slice).map(|entry| out.copy_from_slice(entry)),                    progress.add_child("Resolving"),                    progress.add_child("Decoding"),                    thread_limit,                    &should_interrupt,                    pack.pack_end() as u64,                    || (new_processor(), [0u8; 64]),                    |data,                     progress,                     Context {                         entry: pack_entry,                         entry_end,                         decompressed: bytes,                         state: (ref mut processor, ref mut header_buf),                         level,                     }| {                        let object_kind = pack_entry.header.as_kind().expect("non-delta object");                        data.level = level;                        data.decompressed_size = pack_entry.decompressed_size;                        data.object_kind = object_kind;                        data.compressed_size = entry_end - pack_entry.data_offset;                        data.object_size = bytes.len() as u64;                        let result = crate::index::traverse::process_entry(                            check,                            object_kind,                            bytes,                            progress,                            header_buf,                            &data.index_entry,                            || {                                // TODO: Fix this - we overwrite the header of 'data' which also changes the computed entry size,                                // causing index and pack to seemingly mismatch. This is surprising, and should be done differently.                                // debug_assert_eq!(&data.index_entry.pack_offset, &pack_entry.pack_offset());                                git_features::hash::crc32(                                    pack.entry_slice(data.index_entry.pack_offset..entry_end)                                        .expect("slice pointing into the pack (by now data is verified)"),                                )                            },                            processor,                        );                        match result {                            Err(err @ Error::PackDecode { .. }) if !check.fatal_decode_error() => {                                progress.info(format!("Ignoring decode error: {}", err));                                Ok(())                            }                            res => res,                        }                    },                )?);                outcome.pack_size = pack.data_len() as u64;                Ok(outcome)            },        );        let id = verify_result?;        let outcome = traversal_result?;        Ok((id, outcome, progress))    }} struct EntryWithDefault {    index_entry: crate::index::Entry,    object_kind: git_object::Kind,    object_size: u64,    decompressed_size: u64,    compressed_size: u64,    level: u16,} impl Default for EntryWithDefault {    fn default() -> Self {        EntryWithDefault {            index_entry: crate::index::Entry {                pack_offset: 0,                crc32: None,                oid: git_hash::ObjectId::null_sha1(),            },            level: 0,            object_kind: git_object::Kind::Tree,            object_size: 0,            decompressed_size: 0,            compressed_size: 0,        }    }} impl From<crate::index::Entry> for EntryWithDefault {    fn from(index_entry: crate::index::Entry) -> Self {        EntryWithDefault {            index_entry,            level: 0,            object_kind: git_object::Kind::Tree,            object_size: 0,            decompressed_size: 0,            compressed_size: 0,        }    }} fn digest_statistics(items: VecDeque<crate::cache::delta::Item<EntryWithDefault>>) -> index::traverse::Outcome {    let mut res = index::traverse::Outcome::default();    let average = &mut res.average;    for item in &items {        res.total_compressed_entries_size += item.data.compressed_size;        res.total_decompressed_entries_size += item.data.decompressed_size;        res.total_object_size += item.data.object_size;        *res.objects_per_chain_length.entry(item.data.level as u32).or_insert(0) += 1;         average.decompressed_size += item.data.decompressed_size;        average.compressed_size += item.data.compressed_size as usize;        average.object_size += item.data.object_size;        average.num_deltas += item.data.level as u32;        use git_object::Kind::*;        match item.data.object_kind {            Blob => res.num_blobs += 1,            Tree => res.num_trees += 1,            Tag => res.num_tags += 1,            Commit => res.num_commits += 1,        };    }     average.decompressed_size /= items.len() as u64;    average.compressed_size /= items.len();    average.object_size /= items.len() as u64;    average.num_deltas /= items.len() as u32;     res}