use crate::key_mapper::{hash_prefix_map, hash_prefix_unmap};
use crate::transport::{SharedTransport, Transport, TransportError};
use crate::weave::{read_weave_v5, write_weave_v5, WeaveFile};
use super::format::RepositoryFormat;
use super::pack_2a::RepositoryError;
use crate::declare_repository_format;
use crate::xml_serializer::{XMLInventorySerializer5, XMLRevisionSerializer5};
declare_repository_format! {
FORMAT_WEAVE_6 {
format_string: b"Bazaar-NG branch, format 6\n",
description: "Weave repository format 6 (all-in-one)",
revision_serializer: &XMLRevisionSerializer5,
inventory_serializer: &XMLInventorySerializer5,
all_in_one: true,
supported: true,
deprecated: true,
}
}
pub struct WeaveRepository {
format: &'static RepositoryFormat,
transport: SharedTransport,
}
impl WeaveRepository {
pub fn open(
transport: SharedTransport,
format: &'static RepositoryFormat,
) -> Result<Self, RepositoryError> {
if !format.is_all_in_one() {
return Err(RepositoryError::UnsupportedFormat(
format.get_format_description(),
));
}
Ok(WeaveRepository { format, transport })
}
pub fn create(
transport: SharedTransport,
format: &'static RepositoryFormat,
) -> Result<Self, RepositoryError> {
if !format.is_all_in_one() {
return Err(RepositoryError::UnsupportedFormat(
format.get_format_description(),
));
}
transport.mkdir("weaves")?;
transport.mkdir("revision-store")?;
transport.put_bytes(
"inventory.weave",
&write_weave_v5(&WeaveFile::default()),
None,
)?;
Self::open(transport, format)
}
pub fn format(&self) -> &'static RepositoryFormat {
self.format
}
fn inventory_weave(&self) -> Result<WeaveFile, RepositoryError> {
let data = self.transport.get_bytes("inventory.weave")?;
read_weave_v5(&data)
.map_err(|e| RepositoryError::Corrupt(format!("inventory.weave: {e:?}")))
}
fn weave_text(weave: &WeaveFile, revision_id: &[u8]) -> Result<Vec<u8>, RepositoryError> {
let idx = weave
.lookup(revision_id)
.ok_or_else(|| RepositoryError::NoSuchRevision(revision_id.to_vec()))?;
let lines = weave
.get_lines(idx)
.map_err(|e| RepositoryError::Corrupt(format!("weave get_lines: {e:?}")))?;
Ok(lines.concat())
}
fn revision_store_path(revision_id: &[u8], suffix: &str) -> String {
format!("revision-store/{}{}", hash_prefix_map(revision_id), suffix)
}
pub fn all_revision_ids(&self) -> Result<Vec<Vec<u8>>, RepositoryError> {
let sub = self.transport.subtransport("revision-store")?;
let mut ids = Vec::new();
for rel in sub.iter_files_recursive()? {
let rel = rel.strip_suffix(".gz").unwrap_or(&rel);
if rel.ends_with(".sig") {
continue;
}
ids.push(hash_prefix_unmap(rel));
}
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 mut out = std::collections::HashMap::with_capacity(revision_ids.len());
for revid in revision_ids {
match self.get_revision(revid) {
Ok(rev) => {
let parents = rev
.parent_ids
.iter()
.map(|p| p.as_bytes().to_vec())
.collect();
out.insert(revid.clone(), parents);
}
Err(RepositoryError::NoSuchRevision(_)) => {}
Err(e) => return Err(e),
}
}
Ok(out)
}
pub fn get_revision(
&self,
revision_id: &[u8],
) -> Result<crate::revision::Revision, RepositoryError> {
use crate::serializer::RevisionSerializer;
let bytes = self.read_revision_store_text(revision_id)?;
crate::xml_serializer::XMLRevisionSerializer5
.read_revision_from_string(&bytes)
.map_err(|e| RepositoryError::Corrupt(format!("revision parse: {e:?}")))
}
fn read_revision_store_text(&self, revision_id: &[u8]) -> Result<Vec<u8>, RepositoryError> {
let plain = Self::revision_store_path(revision_id, "");
match self.transport.get_bytes(&plain) {
Ok(b) => Ok(b),
Err(TransportError::NoSuchFile(_)) => {
let gz = Self::revision_store_path(revision_id, ".gz");
match self.transport.get_bytes(&gz) {
Ok(b) => gunzip(&b)
.map_err(|e| RepositoryError::Corrupt(format!("gunzip revision: {e}"))),
Err(TransportError::NoSuchFile(_)) => {
Err(RepositoryError::NoSuchRevision(revision_id.to_vec()))
}
Err(e) => Err(e.into()),
}
}
Err(e) => Err(e.into()),
}
}
pub fn get_inventory(
&self,
revision_id: &[u8],
) -> Result<crate::inventory::MutableInventory, RepositoryError> {
use crate::serializer::InventorySerializer;
let weave = self.inventory_weave()?;
let xml = Self::weave_text(&weave, revision_id)?;
crate::xml_serializer::XMLInventorySerializer5
.read_inventory_from_lines(
&[xml.as_slice()],
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> {
let path = format!("weaves/{}.weave", hash_prefix_map(file_id));
let data = self.transport.get_bytes(&path)?;
let weave =
read_weave_v5(&data).map_err(|e| RepositoryError::Corrupt(format!("{path}: {e:?}")))?;
Self::weave_text(&weave, revision)
}
pub fn get_signature_text(
&self,
revision_id: &[u8],
) -> Result<Option<Vec<u8>>, RepositoryError> {
let path = Self::revision_store_path(revision_id, ".sig");
match self.transport.get_bytes(&path) {
Ok(b) => Ok(Some(b)),
Err(TransportError::NoSuchFile(_)) => Ok(None),
Err(e) => Err(e.into()),
}
}
fn ensure_parent_dir(&self, path: &str) -> Result<(), RepositoryError> {
if let Some((dir, _)) = path.rsplit_once('/') {
self.transport.mkdir(dir)?;
}
Ok(())
}
fn read_or_empty_weave(&self, path: &str) -> Result<WeaveFile, RepositoryError> {
match self.transport.get_bytes(path) {
Ok(data) => {
read_weave_v5(&data).map_err(|e| RepositoryError::Corrupt(format!("{path}: {e:?}")))
}
Err(TransportError::NoSuchFile(_)) => Ok(WeaveFile::default()),
Err(e) => Err(e.into()),
}
}
fn weave_add_version(
&self,
path: &str,
version_id: &[u8],
parents: &[&[u8]],
lines: &[Vec<u8>],
) -> Result<(), RepositoryError> {
let mut weave = self.read_or_empty_weave(path)?;
weave
.add_lines(version_id, parents, lines, None, None)
.map_err(|e| RepositoryError::Corrupt(format!("{path}: add_lines: {e}")))?;
self.ensure_parent_dir(path)?;
self.transport
.put_bytes(path, &write_weave_v5(&weave), None)?;
Ok(())
}
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 path = Self::revision_store_path(revision.revision_id.as_bytes(), "");
self.ensure_parent_dir(&path)?;
self.transport.put_bytes(&path, &bytes, None)?;
Ok(())
}
fn store_inventory(
&mut self,
revision_id: &[u8],
parents: &[Vec<u8>],
inv: &crate::inventory::MutableInventory,
) -> Result<Vec<u8>, RepositoryError> {
use crate::serializer::InventorySerializer;
let lines = crate::xml_serializer::XMLInventorySerializer5
.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 parent_refs: Vec<&[u8]> = parents.iter().map(|p| p.as_slice()).collect();
self.weave_add_version("inventory.weave", revision_id, &parent_refs, &lines)?;
Ok(sha1)
}
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)
}
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 add_text(
&mut self,
file_id: &[u8],
revision: &[u8],
parents: &[(Vec<u8>, Vec<u8>)],
bytes: &[u8],
) -> Result<(), RepositoryError> {
let path = format!("weaves/{}.weave", hash_prefix_map(file_id));
let parent_refs: Vec<&[u8]> = parents.iter().map(|(_, r)| r.as_slice()).collect();
self.weave_add_version(&path, revision, &parent_refs, &split_lines(bytes))
}
fn add_signature_text(
&mut self,
revision_id: &[u8],
signature: &[u8],
) -> Result<(), RepositoryError> {
let path = Self::revision_store_path(revision_id, ".sig");
self.ensure_parent_dir(&path)?;
self.transport.put_bytes(&path, signature, None)?;
Ok(())
}
}
impl super::Repository for WeaveRepository {
fn format(&self) -> &'static RepositoryFormat {
WeaveRepository::format(self)
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn all_revision_ids(&self) -> Result<Vec<Vec<u8>>, RepositoryError> {
WeaveRepository::all_revision_ids(self)
}
fn get_parent_map(
&self,
revision_ids: &[Vec<u8>],
) -> Result<std::collections::HashMap<Vec<u8>, Vec<Vec<u8>>>, RepositoryError> {
WeaveRepository::get_parent_map(self, revision_ids)
}
fn get_revision(
&self,
revision_id: &[u8],
) -> Result<crate::revision::Revision, RepositoryError> {
WeaveRepository::get_revision(self, revision_id)
}
fn get_inventory(
&self,
revision_id: &[u8],
) -> Result<Box<dyn crate::inventory::Inventory>, RepositoryError> {
Ok(Box::new(WeaveRepository::get_inventory(self, revision_id)?))
}
fn get_file_text(&self, file_id: &[u8], revision: &[u8]) -> Result<Vec<u8>, RepositoryError> {
WeaveRepository::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> {
WeaveRepository::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> {
WeaveRepository::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> {
WeaveRepository::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> {
WeaveRepository::add_text(self, file_id, revision, parents, bytes)
}
fn add_signature_text(
&mut self,
revision_id: &[u8],
signature: &[u8],
) -> Result<(), RepositoryError> {
WeaveRepository::add_signature_text(self, revision_id, signature)
}
fn get_signature_text(&self, revision_id: &[u8]) -> Result<Option<Vec<u8>>, RepositoryError> {
WeaveRepository::get_signature_text(self, revision_id)
}
fn commit_write_group(&mut self) -> Result<(), RepositoryError> {
Ok(())
}
}
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 gunzip(data: &[u8]) -> std::io::Result<Vec<u8>> {
use std::io::Read;
let mut out = Vec::new();
flate2::read::GzDecoder::new(data).read_to_end(&mut out)?;
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::transport::LocalTransport;
use std::sync::Arc;
fn temp_repo() -> (tempfile::TempDir, SharedTransport) {
let dir = tempfile::tempdir().unwrap();
let t: SharedTransport = Arc::new(LocalTransport::new(dir.path()));
(dir, t)
}
#[test]
fn create_rejects_non_weave_format() {
let (_d, t) = temp_repo();
let fmt = super::super::format::find_format(
b"Bazaar repository format 2a (needs bzr 1.16 or later)\n",
)
.unwrap();
assert!(WeaveRepository::create(t, fmt).is_err());
}
}