use crate::key_mapper::{ConstantMapper, HashPrefixMapper};
use crate::knit::{
KndxIndex, KnitAnnotateFactory, KnitFactory, KnitKey, KnitKeyAccess, KnitPlainFactory,
KnitVersionedFiles,
};
use crate::transport::SharedTransport;
use super::format::RepositoryFormat;
use super::pack_2a::RepositoryError;
use crate::declare_repository_format;
use crate::xml_serializer::{
XMLInventorySerializer5, XMLInventorySerializer6, XMLInventorySerializer7,
XMLRevisionSerializer5,
};
declare_repository_format! {
FORMAT_KNIT_1 {
format_string: b"Bazaar-NG Knit Repository Format 1",
description: "Knit repository format 1",
revision_serializer: &XMLRevisionSerializer5,
inventory_serializer: &XMLInventorySerializer5,
open: open_knit,
create: create_knit,
supported: true,
deprecated: true,
}
}
declare_repository_format! {
FORMAT_KNIT_3 {
format_string: b"Bazaar Knit Repository Format 3 (bzr 0.15)\n",
description: "Knit repository format 3 (rich root, subtrees)",
revision_serializer: &XMLRevisionSerializer5,
inventory_serializer: &XMLInventorySerializer7,
open: open_knit,
create: create_knit,
rich_root_data: true,
supports_tree_reference: true,
supported: true,
deprecated: true,
}
}
declare_repository_format! {
FORMAT_KNIT_4 {
format_string: b"Bazaar Knit Repository Format 4 (bzr 1.0)\n",
description: "Knit repository format 4 (rich root)",
revision_serializer: &XMLRevisionSerializer5,
inventory_serializer: &XMLInventorySerializer6,
open: open_knit,
create: create_knit,
rich_root_data: true,
supported: true,
deprecated: true,
}
}
type KnitStore<M, F> =
KnitVersionedFiles<KndxIndex<SharedTransport, M>, KnitKeyAccess<SharedTransport, M>, F>;
fn make_store<M, F>(transport: &SharedTransport, mapper: M, factory: F) -> KnitStore<M, F>
where
M: crate::key_mapper::Mapper + Clone,
F: KnitFactory,
{
let index = KndxIndex::new(transport.clone(), mapper.clone());
let access = KnitKeyAccess::new(transport.clone(), mapper);
KnitVersionedFiles::new(index, access, factory, 200)
}
pub struct KnitRepository {
format: &'static RepositoryFormat,
revisions: KnitStore<ConstantMapper, KnitPlainFactory>,
inventories: KnitStore<ConstantMapper, KnitPlainFactory>,
signatures: KnitStore<ConstantMapper, KnitPlainFactory>,
texts: KnitStore<HashPrefixMapper, KnitAnnotateFactory>,
}
impl KnitRepository {
pub fn open(transport: SharedTransport) -> Result<Self, RepositoryError> {
let format = check_format(transport.as_ref())?;
let revisions = make_store(
&transport,
ConstantMapper {
result: "revisions".into(),
},
KnitPlainFactory,
);
let inventories = make_store(
&transport,
ConstantMapper {
result: "inventory".into(),
},
KnitPlainFactory,
);
let signatures = make_store(
&transport,
ConstantMapper {
result: "signatures".into(),
},
KnitPlainFactory,
);
for store in [&revisions, &inventories] {
store
.index()
.load_prefix_typed(Vec::new())
.map_err(|e| RepositoryError::Corrupt(format!("load kndx: {e:?}")))?;
}
let knits = transport
.subtransport("knits")
.map_err(|e| RepositoryError::Corrupt(format!("knits subtransport: {e}")))?;
Ok(KnitRepository {
format,
revisions,
inventories,
signatures,
texts: make_store(&knits, HashPrefixMapper, KnitAnnotateFactory),
})
}
pub fn create(
transport: SharedTransport,
format: &'static RepositoryFormat,
) -> Result<Self, RepositoryError> {
if !std::ptr::fn_addr_eq(format.open, open_knit as super::format::OpenFn) {
return Err(RepositoryError::UnsupportedFormat(
format.get_format_description(),
));
}
transport.mkdir("")?;
transport.mkdir("knits")?;
transport.put_bytes("format", format.format_string(), None)?;
Self::open(transport)
}
pub fn format(&self) -> &'static RepositoryFormat {
self.format
}
fn inventory_serializer(&self) -> &'static dyn crate::serializer::InventorySerializer {
self.format.inventory_serializer
}
pub fn all_revision_ids(&self) -> Result<Vec<Vec<u8>>, RepositoryError> {
let mut ids: Vec<Vec<u8>> = self
.revisions
.keys()
.map_err(|e| RepositoryError::Corrupt(format!("revision keys: {e}")))?
.into_iter()
.filter_map(|k| k.into_iter().next())
.collect();
ids.sort();
Ok(ids)
}
pub fn get_parent_map(
&self,
revision_ids: &[Vec<u8>],
) -> Result<std::collections::HashMap<Vec<u8>, Vec<Vec<u8>>>, RepositoryError> {
let keys: Vec<crate::knit::KnitKey> =
revision_ids.iter().map(|r| vec![r.clone()]).collect();
let raw = self
.revisions
.get_parent_map(&keys)
.map_err(|e| RepositoryError::Corrupt(format!("parent map: {e}")))?;
Ok(super::unkey_knit_parent_map(raw))
}
pub fn get_revision(
&self,
revision_id: &[u8],
) -> Result<crate::revision::Revision, RepositoryError> {
use crate::serializer::RevisionSerializer;
let key: KnitKey = vec![revision_id.to_vec()];
let bytes = self
.revisions
.get_text(&key)
.map_err(|_| RepositoryError::NoSuchRevision(revision_id.to_vec()))?;
crate::xml_serializer::XMLRevisionSerializer5
.read_revision_from_string(&bytes)
.map_err(|e| RepositoryError::Corrupt(format!("revision parse: {e:?}")))
}
pub fn get_inventory(
&self,
revision_id: &[u8],
) -> Result<crate::inventory::MutableInventory, RepositoryError> {
let key: KnitKey = vec![revision_id.to_vec()];
let xml = self
.inventories
.get_text(&key)
.map_err(|e| RepositoryError::Corrupt(format!("inventory: {e}")))?;
let lines: Vec<Vec<u8>> = split_lines(&xml);
let line_refs: Vec<&[u8]> = lines.iter().map(|l| l.as_slice()).collect();
self.inventory_serializer()
.read_inventory_from_lines(&line_refs, Some(crate::RevisionId::from(revision_id)))
.map_err(|e| RepositoryError::Corrupt(format!("inventory parse: {e:?}")))
}
pub fn get_file_text(
&self,
file_id: &[u8],
revision: &[u8],
) -> Result<Vec<u8>, RepositoryError> {
self.texts
.index()
.load_prefix_typed(vec![file_id.to_vec()])
.map_err(|e| RepositoryError::Corrupt(format!("load text kndx: {e:?}")))?;
let key: KnitKey = vec![file_id.to_vec(), revision.to_vec()];
self.texts
.get_text(&key)
.map_err(|e| RepositoryError::Corrupt(format!("text: {e}")))
}
pub fn add_revision(
&mut self,
revision: &crate::revision::Revision,
parents: &[Vec<u8>],
) -> Result<(), RepositoryError> {
use crate::serializer::RevisionSerializer;
let bytes = crate::xml_serializer::XMLRevisionSerializer5
.write_revision_to_string(revision)
.map_err(|e| RepositoryError::Corrupt(format!("write revision: {e:?}")))?;
let key: KnitKey = vec![revision.revision_id.as_bytes().to_vec()];
let parent_keys: Vec<KnitKey> = parents.iter().map(|p| vec![p.clone()]).collect();
self.revisions
.add_lines(key, parent_keys, split_lines(&bytes), false)
.map_err(|e| RepositoryError::Corrupt(format!("add revision: {e}")))?;
Ok(())
}
pub fn add_signature(
&mut self,
revision_id: &[u8],
signature: &[u8],
) -> Result<(), RepositoryError> {
let key: KnitKey = vec![revision_id.to_vec()];
self.signatures
.add_lines(key, Vec::new(), split_lines(signature), false)
.map_err(|e| RepositoryError::Corrupt(format!("add signature: {e}")))?;
Ok(())
}
pub fn get_signature_text(
&self,
revision_id: &[u8],
) -> Result<Option<Vec<u8>>, RepositoryError> {
let key: KnitKey = vec![revision_id.to_vec()];
match self.signatures.get_text(&key) {
Ok(bytes) => Ok(Some(bytes)),
Err(crate::knit::KnitError::RevisionNotPresent(_)) => Ok(None),
Err(e) => Err(RepositoryError::Corrupt(format!("signature: {e}"))),
}
}
pub fn add_inventory_from_entries(
&mut self,
revision_id: &[u8],
parents: &[Vec<u8>],
_root_id: &[u8],
entries: &[crate::inventory::Entry],
) -> Result<Vec<u8>, RepositoryError> {
let mut inv = crate::inventory::MutableInventory::new();
inv.revision_id = Some(crate::RevisionId::from(revision_id));
for entry in entries {
inv.add(entry.clone())
.map_err(|e| RepositoryError::Corrupt(format!("build inventory: {e:?}")))?;
}
self.store_inventory(revision_id, parents, &inv)
}
pub fn add_inventory_by_delta(
&mut self,
basis_revision_id: &[u8],
delta: &crate::inventory_delta::InventoryDelta,
new_revision_id: &[u8],
parents: &[Vec<u8>],
) -> Result<Vec<u8>, RepositoryError> {
let basis = if basis_revision_id == crate::branch::NULL_REVISION {
crate::inventory::MutableInventory::new()
} else {
self.get_inventory(basis_revision_id)?
};
let new_inv = basis
.create_by_apply_delta(delta, crate::RevisionId::from(new_revision_id))
.map_err(|e| RepositoryError::Corrupt(format!("apply inventory delta: {e:?}")))?;
self.store_inventory(new_revision_id, parents, &new_inv)
}
fn store_inventory(
&mut self,
revision_id: &[u8],
parents: &[Vec<u8>],
inv: &crate::inventory::MutableInventory,
) -> Result<Vec<u8>, RepositoryError> {
let lines = self
.inventory_serializer()
.write_inventory_to_lines(inv, false)
.map_err(|e| RepositoryError::Corrupt(format!("serialise inventory: {e:?}")))?;
let line_refs: Vec<&[u8]> = lines.iter().map(|l| l.as_slice()).collect();
let sha1 = crate::weave::sha_strings(&line_refs);
let key: KnitKey = vec![revision_id.to_vec()];
let parent_keys: Vec<KnitKey> = parents.iter().map(|p| vec![p.clone()]).collect();
self.inventories
.add_lines(key, parent_keys, lines, false)
.map_err(|e| RepositoryError::Corrupt(format!("add inventory: {e}")))?;
Ok(sha1)
}
pub fn add_text(
&mut self,
file_id: &[u8],
revision: &[u8],
parents: &[(Vec<u8>, Vec<u8>)],
bytes: &[u8],
) -> Result<(), RepositoryError> {
self.texts
.index()
.load_prefix_typed(vec![file_id.to_vec()])
.map_err(|e| RepositoryError::Corrupt(format!("load text kndx: {e:?}")))?;
let key: KnitKey = vec![file_id.to_vec(), revision.to_vec()];
let parent_keys: Vec<KnitKey> = parents
.iter()
.map(|(f, r)| vec![f.clone(), r.clone()])
.collect();
self.texts
.add_lines(key, parent_keys, split_lines(bytes), false)
.map_err(|e| RepositoryError::Corrupt(format!("add text: {e}")))?;
Ok(())
}
}
impl super::Repository for KnitRepository {
fn format(&self) -> &'static RepositoryFormat {
KnitRepository::format(self)
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn all_revision_ids(&self) -> Result<Vec<Vec<u8>>, RepositoryError> {
KnitRepository::all_revision_ids(self)
}
fn get_parent_map(
&self,
revision_ids: &[Vec<u8>],
) -> Result<std::collections::HashMap<Vec<u8>, Vec<Vec<u8>>>, RepositoryError> {
KnitRepository::get_parent_map(self, revision_ids)
}
fn get_revision(
&self,
revision_id: &[u8],
) -> Result<crate::revision::Revision, RepositoryError> {
KnitRepository::get_revision(self, revision_id)
}
fn get_inventory(
&self,
revision_id: &[u8],
) -> Result<Box<dyn crate::inventory::Inventory>, RepositoryError> {
Ok(Box::new(KnitRepository::get_inventory(self, revision_id)?))
}
fn get_file_text(&self, file_id: &[u8], revision: &[u8]) -> Result<Vec<u8>, RepositoryError> {
KnitRepository::get_file_text(self, file_id, revision)
}
fn start_write_group(&mut self) -> Result<(), RepositoryError> {
Ok(())
}
fn add_revision(
&mut self,
revision: &crate::revision::Revision,
parents: &[Vec<u8>],
) -> Result<(), RepositoryError> {
KnitRepository::add_revision(self, revision, parents)
}
fn add_inventory_from_entries(
&mut self,
revision_id: &[u8],
parents: &[Vec<u8>],
root_id: &[u8],
entries: &[crate::inventory::Entry],
) -> Result<Vec<u8>, RepositoryError> {
KnitRepository::add_inventory_from_entries(self, revision_id, parents, root_id, entries)
}
fn add_inventory_by_delta(
&mut self,
basis_revision_id: &[u8],
delta: &crate::inventory_delta::InventoryDelta,
new_revision_id: &[u8],
parents: &[Vec<u8>],
) -> Result<Vec<u8>, RepositoryError> {
KnitRepository::add_inventory_by_delta(
self,
basis_revision_id,
delta,
new_revision_id,
parents,
)
}
fn add_text(
&mut self,
file_id: &[u8],
revision: &[u8],
parents: &[(Vec<u8>, Vec<u8>)],
bytes: &[u8],
) -> Result<(), RepositoryError> {
KnitRepository::add_text(self, file_id, revision, parents, bytes)
}
fn add_signature_text(
&mut self,
revision_id: &[u8],
signature: &[u8],
) -> Result<(), RepositoryError> {
KnitRepository::add_signature(self, revision_id, signature)
}
fn get_signature_text(&self, revision_id: &[u8]) -> Result<Option<Vec<u8>>, RepositoryError> {
KnitRepository::get_signature_text(self, revision_id)
}
fn commit_write_group(&mut self) -> Result<(), RepositoryError> {
Ok(())
}
}
fn check_format(
transport: &dyn crate::transport::Transport,
) -> Result<&'static RepositoryFormat, RepositoryError> {
let marker = transport.get_bytes("format")?;
let format = super::format::find_format(&marker)
.ok_or_else(|| RepositoryError::UnknownFormat(marker.clone()))?;
if !std::ptr::fn_addr_eq(format.open, open_knit as super::format::OpenFn) {
return Err(RepositoryError::UnsupportedFormat(
format.get_format_description(),
));
}
Ok(format)
}
pub fn open_knit(
transport: SharedTransport,
) -> Result<Box<dyn super::Repository>, RepositoryError> {
Ok(Box::new(KnitRepository::open(transport)?))
}
pub fn create_knit(
format: &'static RepositoryFormat,
transport: SharedTransport,
) -> Result<Box<dyn super::Repository>, RepositoryError> {
Ok(Box::new(KnitRepository::create(transport, format)?))
}
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
}
#[cfg(test)]
mod tests {
use super::*;
use crate::transport::LocalTransport;
use std::sync::Arc;
fn temp() -> (tempfile::TempDir, SharedTransport) {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("repository");
(dir, Arc::new(LocalTransport::new(&path)))
}
#[test]
fn create_rejects_non_knit_format() {
let (_d, t) = temp();
let fmt = super::super::format::find_format(
b"Bazaar repository format 2a (needs bzr 1.16 or later)\n",
)
.unwrap();
assert!(KnitRepository::create(t, fmt).is_err());
}
}