use super::{traverse, Error};
use crate::exact_vec;
pub struct Item<T> {
pub offset: crate::data::Offset,
pub next_offset: crate::data::Offset,
pub data: T,
children: Vec<u32>,
}
impl<T> Item<T> {
pub fn children(&self) -> &[u32] {
&self.children
}
}
enum NodeKind {
Root,
Child,
}
pub struct Tree<T> {
root_items: Vec<Item<T>>,
child_items: Vec<Item<T>>,
last_seen: Option<NodeKind>,
future_child_offsets: Vec<(crate::data::Offset, usize)>,
}
impl<T> Tree<T> {
pub fn with_capacity(num_objects: usize) -> Result<Self, Error> {
Ok(Tree {
root_items: exact_vec(num_objects / 2),
child_items: exact_vec(num_objects / 2),
last_seen: None,
future_child_offsets: Vec::new(),
})
}
pub(super) fn num_items(&self) -> usize {
self.root_items.len() + self.child_items.len()
}
pub(super) fn take_root_and_child(self) -> (Vec<Item<T>>, Vec<Item<T>>) {
(self.root_items, self.child_items)
}
pub(super) fn assert_is_incrementing_and_update_next_offset(
&mut self,
offset: crate::data::Offset,
) -> Result<(), Error> {
let items = match &self.last_seen {
Some(NodeKind::Root) => &mut self.root_items,
Some(NodeKind::Child) => &mut self.child_items,
None => return Ok(()),
};
let item = &mut items.last_mut().expect("last seen won't lie");
if offset <= item.offset {
return Err(Error::InvariantIncreasingPackOffset {
last_pack_offset: item.offset,
pack_offset: offset,
});
}
item.next_offset = offset;
Ok(())
}
pub(super) fn set_pack_entries_end_and_resolve_ref_offsets(
&mut self,
pack_entries_end: crate::data::Offset,
) -> Result<(), traverse::Error> {
if !self.future_child_offsets.is_empty() {
for (parent_offset, child_index) in self.future_child_offsets.drain(..) {
if let Ok(i) = self.child_items.binary_search_by_key(&parent_offset, |i| i.offset) {
self.child_items[i].children.push(child_index as u32);
} else if let Ok(i) = self.root_items.binary_search_by_key(&parent_offset, |i| i.offset) {
self.root_items[i].children.push(child_index as u32);
} else {
return Err(traverse::Error::OutOfPackRefDelta {
base_pack_offset: parent_offset,
});
}
}
}
self.assert_is_incrementing_and_update_next_offset(pack_entries_end)
.expect("BUG: pack now is smaller than all previously seen entries");
Ok(())
}
pub fn add_root(&mut self, offset: crate::data::Offset, data: T) -> Result<(), Error> {
self.assert_is_incrementing_and_update_next_offset(offset)?;
self.last_seen = NodeKind::Root.into();
self.root_items.push(Item {
offset,
next_offset: 0,
data,
children: Default::default(),
});
Ok(())
}
pub fn add_child(
&mut self,
base_offset: crate::data::Offset,
offset: crate::data::Offset,
data: T,
) -> Result<(), Error> {
self.assert_is_incrementing_and_update_next_offset(offset)?;
let next_child_index = self.child_items.len();
if let Ok(i) = self.child_items.binary_search_by_key(&base_offset, |i| i.offset) {
self.child_items[i].children.push(next_child_index as u32);
} else if let Ok(i) = self.root_items.binary_search_by_key(&base_offset, |i| i.offset) {
self.root_items[i].children.push(next_child_index as u32);
} else {
self.future_child_offsets.push((base_offset, next_child_index));
}
self.last_seen = NodeKind::Child.into();
self.child_items.push(Item {
offset,
next_offset: 0,
data,
children: Default::default(),
});
Ok(())
}
}
#[cfg(test)]
mod tests {
mod from_offsets_in_pack {
use std::sync::atomic::AtomicBool;
use crate as pack;
const SMALL_PACK_INDEX: &str = "objects/pack/pack-a2bf8e71d8c18879e499335762dd95119d93d9f1.idx";
const SMALL_PACK: &str = "objects/pack/pack-a2bf8e71d8c18879e499335762dd95119d93d9f1.pack";
const INDEX_V1: &str = "objects/pack/pack-c0438c19fb16422b6bbcce24387b3264416d485b.idx";
const PACK_FOR_INDEX_V1: &str = "objects/pack/pack-c0438c19fb16422b6bbcce24387b3264416d485b.pack";
use gix_testtools::fixture_path;
#[test]
fn v1() -> Result<(), Box<dyn std::error::Error>> {
tree(INDEX_V1, PACK_FOR_INDEX_V1)
}
#[test]
fn v2() -> Result<(), Box<dyn std::error::Error>> {
tree(SMALL_PACK_INDEX, SMALL_PACK)
}
fn tree(index_path: &str, pack_path: &str) -> Result<(), Box<dyn std::error::Error>> {
let idx = pack::index::File::at(fixture_path(index_path), gix_hash::Kind::Sha1)?;
crate::cache::delta::Tree::from_offsets_in_pack(
&fixture_path(pack_path),
idx.sorted_offsets().into_iter(),
&|ofs| *ofs,
&|id| idx.lookup(id).map(|index| idx.pack_offset_at_index(index)),
&mut gix_features::progress::Discard,
&AtomicBool::new(false),
gix_hash::Kind::Sha1,
)?;
Ok(())
}
}
mod size {
use gix_testtools::size_ok;
use super::super::Item;
#[test]
fn size_of_pack_tree_item() {
let actual = std::mem::size_of::<[Item<()>; 7_500_000]>();
let expected = 300_000_000;
assert!(
size_ok(actual, expected),
"we don't want these to grow unnoticed: {actual} <~ {expected}"
);
}
#[test]
fn size_of_pack_verify_data_structure() {
pub struct EntryWithDefault {
_index_entry: crate::index::Entry,
_kind: gix_object::Kind,
_object_size: u64,
_decompressed_size: u64,
_compressed_size: u64,
_header_size: u16,
_level: u16,
}
let actual = std::mem::size_of::<[Item<EntryWithDefault>; 7_500_000]>();
let sha1 = 840_000_000;
let sha256_extra = 120_000_000;
let expected = sha1 + sha256_extra;
assert!(
size_ok(actual, expected),
"we don't want these to grow unnoticed: {actual} <~ {expected}"
);
}
}
}