use std::sync::{Arc, Mutex};
use crate::btree_builder::BTreeBuilder;
use crate::groupcompress::gcvf::{
GcAccess, GcBuildDetails, GcIndex, GroupCompressVersionedFiles, IndexMemo, ReadMemo,
};
use crate::knit::KnitError;
use crate::pack::ContainerWriter;
use crate::pack_repo::{index_extension, IndexKind};
use crate::transport::Transport;
use crate::versionedfile::Key;
use super::pack_2a::RepositoryError;
type PackName = String;
#[derive(Clone, Copy)]
pub(super) enum RepackTarget {
Revisions,
Inventories,
Texts,
Signatures,
Chk,
}
struct SharedPack {
writer: ContainerWriter<Vec<u8>>,
}
#[derive(Clone)]
struct PackWritingAccess {
pack_name: PackName,
pack: Arc<Mutex<SharedPack>>,
}
impl GcAccess for PackWritingAccess {
type F = PackName;
fn get_raw_records(&self, memos: &[ReadMemo<Self::F>]) -> Result<Vec<Vec<u8>>, KnitError> {
let pack = self.pack.lock().unwrap();
let bytes = pack.writer.get_ref();
let mut out = Vec::with_capacity(memos.len());
for memo in memos {
let start = memo.start as usize;
let stop = memo.stop as usize;
if stop > bytes.len() || start > stop {
return Err(KnitError::Corrupt(format!(
"record range {start}..{stop} outside pack buffer (len {})",
bytes.len()
)));
}
let body = crate::pack::read_bytes_record_body(&bytes[start..stop])
.map_err(|e| KnitError::Corrupt(format!("reading pack record: {e}")))?;
out.push(body);
}
Ok(out)
}
fn add_raw_record(
&self,
_size: usize,
chunks: Vec<Vec<u8>>,
) -> Result<ReadMemo<Self::F>, KnitError> {
let body_len: usize = chunks.iter().map(|c| c.len()).sum();
let refs: Vec<&[u8]> = chunks.iter().map(|c| c.as_slice()).collect();
let mut pack = self.pack.lock().unwrap();
let (start, length) = pack
.writer
.add_bytes_record(&refs, body_len, &[])
.map_err(|e| KnitError::Corrupt(format!("writing pack record: {e}")))?;
Ok(ReadMemo::new(self.pack_name.clone(), start, start + length))
}
}
type IndexRecord = (Key, IndexMemo<PackName>, Option<Vec<Key>>);
struct PackWritingIndex {
has_graph: bool,
records: Mutex<Vec<IndexRecord>>,
}
impl PackWritingIndex {
fn new(has_graph: bool) -> Self {
PackWritingIndex {
has_graph,
records: Mutex::new(Vec::new()),
}
}
fn take_records(&self) -> Vec<IndexRecord> {
std::mem::take(&mut self.records.lock().unwrap())
}
}
impl GcIndex for PackWritingIndex {
type F = PackName;
fn get_build_details(
&self,
keys: &[Key],
) -> Result<std::collections::HashMap<Key, GcBuildDetails<Self::F>>, KnitError> {
let records = self.records.lock().unwrap();
let mut out = std::collections::HashMap::new();
for key in keys {
if let Some((_, memo, parents)) = records.iter().rev().find(|(k, _, _)| k == key) {
out.insert(
key.clone(),
GcBuildDetails {
index_memo: memo.clone(),
parents: parents.clone(),
},
);
}
}
Ok(out)
}
fn get_parent_map(
&self,
keys: &[Key],
) -> Result<std::collections::HashMap<Key, Vec<Key>>, KnitError> {
let records = self.records.lock().unwrap();
let mut out = std::collections::HashMap::new();
for key in keys {
if let Some((_, _, Some(parents))) = records.iter().rev().find(|(k, _, _)| k == key) {
out.insert(key.clone(), parents.clone());
}
}
Ok(out)
}
fn keys(&self) -> Result<Vec<Key>, KnitError> {
Ok(self
.records
.lock()
.unwrap()
.iter()
.map(|(k, _, _)| k.clone())
.collect())
}
fn has_graph(&self) -> bool {
self.has_graph
}
fn check_write_ok(&self) -> Result<(), KnitError> {
Ok(())
}
fn add_records(&self, records: &[IndexRecord], _random_id: bool) -> Result<(), KnitError> {
self.records.lock().unwrap().extend_from_slice(records);
Ok(())
}
}
type WriteStore = GroupCompressVersionedFiles<PackWritingIndex, PackWritingAccess>;
pub(super) struct WriteGroup {
pack: Arc<Mutex<SharedPack>>,
revisions: WriteStore,
inventories: WriteStore,
texts: WriteStore,
signatures: WriteStore,
chk_bytes: Arc<WriteStore>,
}
impl WriteGroup {
pub(super) fn new(
pack_name: &str,
chk_fallback: Option<Arc<dyn crate::versionedfile::VersionedFiles + Send + Sync>>,
) -> Result<Self, RepositoryError> {
let mut writer = ContainerWriter::new(Vec::new());
writer
.begin()
.map_err(|e| RepositoryError::Corrupt(format!("pack begin: {e}")))?;
let pack = Arc::new(Mutex::new(SharedPack { writer }));
let make = |has_graph: bool| -> WriteStore {
let access = PackWritingAccess {
pack_name: pack_name.to_string(),
pack: pack.clone(),
};
GroupCompressVersionedFiles::new(PackWritingIndex::new(has_graph), access, false)
};
let revisions = make(true);
let inventories = make(true);
let texts = make(true);
let signatures = make(false);
let mut chk_store = make(false);
if let Some(fallback) = chk_fallback {
chk_store.add_fallback_versioned_files(Box::new(fallback));
}
let chk_bytes = Arc::new(chk_store);
Ok(WriteGroup {
pack,
revisions,
inventories,
texts,
signatures,
chk_bytes,
})
}
pub(super) fn add_signature(
&self,
revision_id: &[u8],
signature: &[u8],
) -> Result<(), RepositoryError> {
let key = Key::fixed(vec![revision_id.to_vec()]);
self.signatures
.add_lines(key, Some(Vec::new()), split_lines(signature))?;
Ok(())
}
pub(super) fn add_revision(
&self,
revision_id: &[u8],
parents: &[Vec<u8>],
bytes: &[u8],
) -> Result<(), RepositoryError> {
let key = Key::fixed(vec![revision_id.to_vec()]);
let parent_keys: Vec<Key> = parents
.iter()
.map(|p| Key::fixed(vec![p.clone()]))
.collect();
self.revisions
.add_lines(key, Some(parent_keys), split_lines(bytes))?;
Ok(())
}
pub(super) fn add_inventory(
&self,
revision_id: &[u8],
parents: &[Vec<u8>],
bytes: &[u8],
) -> Result<(), RepositoryError> {
let key = Key::fixed(vec![revision_id.to_vec()]);
let parent_keys: Vec<Key> = parents
.iter()
.map(|p| Key::fixed(vec![p.clone()]))
.collect();
self.inventories
.add_lines(key, Some(parent_keys), split_lines(bytes))?;
Ok(())
}
pub(super) fn add_inventory_from_entries(
&self,
revision_id: &[u8],
parents: &[Vec<u8>],
root_id: &[u8],
entries: &[crate::inventory::Entry],
) -> Result<Vec<u8>, RepositoryError> {
let cache: std::sync::Arc<dyn crate::chk_map::PageCache> =
std::sync::Arc::new(crate::chk_map::InMemoryPageCache::new());
let inv = crate::chk_inventory::CHKInventory::from_inventory(
self.chk_bytes.clone(),
cache,
crate::RevisionId::from(revision_id),
crate::FileId::from(root_id),
entries,
65536,
b"hash-255-way".to_vec(),
)
.map_err(|e| RepositoryError::Corrupt(format!("building chk inventory: {e:?}")))?;
let lines = inv
.to_lines()
.map_err(|e| RepositoryError::Corrupt(format!("serialising chk inventory: {e:?}")))?;
let inv_bytes: Vec<u8> = lines.concat();
let sha1 = crate::weave::sha_strings(&lines);
self.add_inventory(revision_id, parents, &inv_bytes)?;
Ok(sha1)
}
pub(super) fn add_inventory_by_delta(
&self,
basis_revision_id: &[u8],
basis_lines: &[Vec<u8>],
delta: &crate::inventory_delta::InventoryDelta,
new_revision_id: &[u8],
parents: &[Vec<u8>],
) -> Result<Vec<u8>, RepositoryError> {
let cache: std::sync::Arc<dyn crate::chk_map::PageCache> =
std::sync::Arc::new(crate::chk_map::InMemoryPageCache::new());
let basis = crate::chk_inventory::CHKInventory::deserialise(
self.chk_bytes.clone(),
cache,
basis_lines,
&crate::RevisionId::from(basis_revision_id),
)
.map_err(|e| RepositoryError::Corrupt(format!("basis inventory deserialise: {e:?}")))?;
let new_inv = basis
.create_by_apply_delta(delta, crate::RevisionId::from(new_revision_id), true)
.map_err(|e| RepositoryError::Corrupt(format!("apply inventory delta: {e:?}")))?;
let lines = new_inv
.to_lines()
.map_err(|e| RepositoryError::Corrupt(format!("serialising chk inventory: {e:?}")))?;
let inv_bytes: Vec<u8> = lines.concat();
let sha1 = crate::weave::sha_strings(&lines);
self.add_inventory(new_revision_id, parents, &inv_bytes)?;
Ok(sha1)
}
pub(super) fn add_text(
&self,
file_id: &[u8],
revision: &[u8],
parents: &[(Vec<u8>, Vec<u8>)],
bytes: &[u8],
) -> Result<(), RepositoryError> {
let key = Key::fixed(vec![file_id.to_vec(), revision.to_vec()]);
let parent_keys: Vec<Key> = parents
.iter()
.map(|(f, r)| Key::fixed(vec![f.clone(), r.clone()]))
.collect();
self.texts
.add_lines(key, Some(parent_keys), split_lines(bytes))?;
Ok(())
}
pub(super) fn copy_store(
&self,
source: &dyn crate::versionedfile::VersionedFiles,
target: RepackTarget,
) -> Result<(), RepositoryError> {
let mut keys = source.keys()?;
keys.sort_by(|a, b| a.segments().cmp(b.segments()));
self.copy_store_keys(source, target, &keys)
}
pub(super) fn copy_store_keys(
&self,
source: &dyn crate::versionedfile::VersionedFiles,
target: RepackTarget,
keys: &[crate::versionedfile::Key],
) -> Result<(), RepositoryError> {
let store = match target {
RepackTarget::Revisions => &self.revisions,
RepackTarget::Inventories => &self.inventories,
RepackTarget::Texts => &self.texts,
RepackTarget::Signatures => &self.signatures,
RepackTarget::Chk => self.chk_bytes.as_ref(),
};
for record in source.get_record_stream(keys, "unordered", false)? {
let record = record?;
if record.storage_kind() == "absent" {
continue;
}
let key = record.key();
let parents = record.parents();
let lines: Vec<Vec<u8>> = record.to_lines().map(|l| l.into_owned()).collect();
store.add_lines(key, parents, lines)?;
}
Ok(())
}
pub(super) fn add_chk_page(
&self,
page_key: &[u8],
page_bytes: Vec<u8>,
) -> Result<(), RepositoryError> {
let key = Key::fixed(vec![page_key.to_vec()]);
let lines = split_lines(&page_bytes);
self.chk_bytes.add_lines(key, None, lines)?;
Ok(())
}
pub(super) fn finish(
self,
transport: &dyn Transport,
existing_packs: &[(String, Vec<u8>)],
) -> Result<Option<(String, Vec<u8>)>, RepositoryError> {
let rix = serialise_index(&self.revisions, 1)?;
let iix = serialise_index(&self.inventories, 1)?;
let tix = serialise_index(&self.texts, 2)?;
let six = serialise_index(&self.signatures, 1)?;
let cix = serialise_index(self.chk_bytes.as_ref(), 1)?;
let pack_bytes = {
let mut pack = self.pack.lock().unwrap();
pack.writer
.end()
.map_err(|e| RepositoryError::Corrupt(format!("pack end: {e}")))?;
std::mem::take(pack.writer.get_mut())
};
let pack_name = md5_hex(&pack_bytes);
transport.put_bytes(&format!("packs/{pack_name}.pack"), &pack_bytes, None)?;
let write_index = |ext: &str, bytes: &[u8]| -> Result<usize, RepositoryError> {
let name = format!("indices/{pack_name}{ext}");
transport.put_bytes(&name, bytes, None)?;
Ok(bytes.len())
};
let sizes = [
write_index(index_extension(IndexKind::Revision), &rix)?,
write_index(index_extension(IndexKind::Inventory), &iix)?,
write_index(index_extension(IndexKind::Text), &tix)?,
write_index(index_extension(IndexKind::Signature), &six)?,
write_index(index_extension(IndexKind::Chk), &cix)?,
];
let new_value = sizes
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>()
.join(" ")
.into_bytes();
let mut names = BTreeBuilder::new(0, 1);
for (name, value) in existing_packs {
names
.add_node(vec![name.clone().into_bytes()], value.clone(), vec![])
.map_err(|e| RepositoryError::Corrupt(format!("pack-names node: {e:?}")))?;
}
names
.add_node(
vec![pack_name.clone().into_bytes()],
new_value.clone(),
vec![],
)
.map_err(|e| RepositoryError::Corrupt(format!("pack-names node: {e:?}")))?;
let names_bytes = names
.finish()
.map_err(|e| RepositoryError::Corrupt(format!("pack-names finish: {e:?}")))?;
transport.put_bytes("pack-names", &names_bytes, None)?;
Ok(Some((pack_name, new_value)))
}
}
fn md5_hex(bytes: &[u8]) -> String {
use md5::{Digest, Md5};
let digest = Md5::digest(bytes);
let mut s = String::with_capacity(32);
for b in digest {
s.push_str(&format!("{b:02x}"));
}
s
}
fn split_lines(bytes: &[u8]) -> Vec<Vec<u8>> {
let mut lines = Vec::new();
let mut start = 0;
for (i, b) in bytes.iter().enumerate() {
if *b == b'\n' {
lines.push(bytes[start..=i].to_vec());
start = i + 1;
}
}
if start < bytes.len() {
lines.push(bytes[start..].to_vec());
}
lines
}
fn serialise_index(store: &WriteStore, key_elements: usize) -> Result<Vec<u8>, RepositoryError> {
let has_graph = store.index().has_graph();
let ref_lists = if has_graph { 1 } else { 0 };
let mut builder = BTreeBuilder::new(ref_lists, key_elements);
let records = store.index().take_records();
let mut seen: std::collections::HashSet<Vec<Vec<u8>>> = std::collections::HashSet::new();
for (key, memo, parents) in records {
if !seen.insert(key.segments().to_vec()) {
continue;
}
let value = format!(
"{} {} {} {}",
memo.read_memo.start,
memo.read_memo.byte_length(),
memo.entry_start,
memo.entry_end
)
.into_bytes();
let references: Vec<Vec<Vec<Vec<u8>>>> = if has_graph {
vec![parents
.unwrap_or_default()
.into_iter()
.map(|k| k.segments().to_vec())
.collect()]
} else {
vec![]
};
builder
.add_node(key.segments().to_vec(), value, references)
.map_err(|e| RepositoryError::Corrupt(format!("index node: {e:?}")))?;
}
builder
.finish()
.map_err(|e| RepositoryError::Corrupt(format!("index finish: {e:?}")))
}