use byteorder::{BigEndian, WriteBytesExt};
use std::borrow::Cow;
use std::collections::HashMap;
use std::convert::TryInto;
#[derive(Debug)]
pub enum Error {
ExistingContent(Key),
VersionNotPresent(VersionId),
Io(std::io::Error),
}
impl From<std::io::Error> for Error {
fn from(e: std::io::Error) -> Error {
Error::Io(e)
}
}
#[cfg(feature = "pyo3")]
impl From<Error> for pyo3::PyErr {
fn from(e: Error) -> pyo3::PyErr {
pyo3::import_exception!(bzrformats.errors, RevisionNotPresent);
pyo3::import_exception!(bzrformats.versionedfile, ExistingContent);
match e {
Error::VersionNotPresent(key) => {
RevisionNotPresent::new_err(format!("Version not present: {:?}", key))
}
Error::ExistingContent(key) => {
ExistingContent::new_err(format!("Existing content: {:?}", key))
}
Error::Io(e) => e.into(),
}
}
}
#[cfg(feature = "pyo3")]
impl From<pyo3::PyErr> for Error {
fn from(e: pyo3::PyErr) -> Error {
use pyo3::prelude::PyAnyMethods;
pyo3::import_exception!(bzrformats.errors, RevisionNotPresent);
pyo3::import_exception!(bzrformats.versionedfile, ExistingContent);
pyo3::Python::attach(|py| {
if e.is_instance_of::<RevisionNotPresent>(py) {
Error::VersionNotPresent(
e.value(py)
.getattr("args")
.unwrap()
.get_item(0)
.unwrap()
.extract()
.unwrap(),
)
} else if e.is_instance_of::<ExistingContent>(py) {
Error::ExistingContent(
e.value(py)
.getattr("args")
.unwrap()
.get_item(0)
.unwrap()
.extract()
.unwrap(),
)
} else {
panic!("Unexpected error: {:?}", e)
}
})
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Error::ExistingContent(key) => write!(f, "Existing content: {:?}", key),
Error::VersionNotPresent(version) => write!(f, "Version not present: {:?}", version),
Error::Io(e) => write!(f, "IO error: {}", e),
}
}
}
impl std::error::Error for Error {}
pub enum Ordering {
Unordered,
Topological,
}
impl ToString for Ordering {
fn to_string(&self) -> String {
match self {
Ordering::Unordered => "unordered".to_string(),
Ordering::Topological => "topological".to_string(),
}
}
}
#[cfg(feature = "pyo3")]
impl<'py> pyo3::IntoPyObject<'py> for Ordering {
type Target = pyo3::types::PyString;
type Output = pyo3::Bound<'py, Self::Target>;
type Error = pyo3::PyErr;
fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self.to_string().into_pyobject(py)?)
}
}
#[cfg(feature = "pyo3")]
impl<'a, 'py> pyo3::FromPyObject<'a, 'py> for Ordering {
type Error = pyo3::PyErr;
fn extract(ob: pyo3::Borrowed<'a, 'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
let s = ob.extract::<String>()?;
match s.as_str() {
"unordered" => Ok(Ordering::Unordered),
"topological" => Ok(Ordering::Topological),
_ => Err(pyo3::exceptions::PyValueError::new_err(format!(
"Expected 'unordered' or 'topological', got '{}'",
s
))),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct VersionId(Vec<u8>);
#[cfg(feature = "pyo3")]
impl<'py> pyo3::IntoPyObject<'py> for &VersionId {
type Target = pyo3::types::PyBytes;
type Output = pyo3::Bound<'py, Self::Target>;
type Error = pyo3::PyErr;
fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
let bytes = pyo3::types::PyBytes::new(py, &self.0);
Ok(bytes.into_pyobject(py)?)
}
}
#[cfg(feature = "pyo3")]
impl<'a, 'py> pyo3::FromPyObject<'a, 'py> for VersionId {
type Error = pyo3::PyErr;
fn extract(ob: pyo3::Borrowed<'a, 'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
let bytes = ob.extract::<Vec<u8>>()?;
Ok(VersionId(bytes))
}
}
impl std::fmt::Display for VersionId {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "VersionId({:?})", self.0)?;
Ok(())
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Key {
Fixed(Vec<Vec<u8>>),
ContentAddressed(Vec<Vec<u8>>),
}
impl Key {
pub fn fixed(segments: Vec<Vec<u8>>) -> Self {
Key::Fixed(segments)
}
pub fn segments(&self) -> &[Vec<u8>] {
match self {
Key::Fixed(v) | Key::ContentAddressed(v) => v,
}
}
fn segments_mut(&mut self) -> &mut Vec<Vec<u8>> {
match self {
Key::Fixed(v) | Key::ContentAddressed(v) => v,
}
}
pub fn version_id(&self) -> &[u8] {
self.segments().last().map(Vec::as_slice).unwrap_or(&[])
}
pub fn prefix(&self) -> &[Vec<u8>] {
let segs = self.segments();
if segs.is_empty() {
&[]
} else {
&segs[..segs.len() - 1]
}
}
pub fn from_prefix_and_suffix(prefix: &[Vec<u8>], suffix: Vec<u8>) -> Self {
let mut v = prefix.to_vec();
v.push(suffix);
Key::Fixed(v)
}
pub fn set_version_id(&mut self, id: Vec<u8>) {
let segs = self.segments_mut();
if let Some(last) = segs.last_mut() {
*last = id;
}
}
pub fn add_prefix(&mut self, prefix: &[&[u8]]) {
let v = self.segments_mut();
for p in prefix.iter().rev() {
v.insert(0, p.to_vec());
}
}
}
impl std::fmt::Display for Key {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Key::Fixed(v) => {
write!(f, "(")?;
for (i, v) in v.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{:?}", v)?;
}
write!(f, ")")
}
Key::ContentAddressed(v) => {
write!(f, "(")?;
for v in v.iter() {
write!(f, "{:?}", v)?;
write!(f, ", ")?;
}
write!(f, "<ContentAddressed>")?;
write!(f, ")")
}
}
}
}
#[cfg(feature = "pyo3")]
impl<'a, 'py> pyo3::FromPyObject<'a, 'py> for Key {
type Error = pyo3::PyErr;
fn extract(ob: pyo3::Borrowed<'a, 'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
use pyo3::prelude::*;
use pyo3::types::PyBytes;
match ob
.get_type()
.name()
.unwrap()
.to_string()
.split('.')
.next_back()
.unwrap()
{
"tuple" | "StaticTuple" => {}
_ => {
return Err(pyo3::exceptions::PyTypeError::new_err(format!(
"Expected tuple or StaticTuple, got {}",
ob.get_type().name().unwrap()
)));
}
}
let mut v = Vec::with_capacity(ob.len()?);
for i in 0..ob.len()? - 1 {
let b = ob.get_item(i)?.extract::<Bound<PyBytes>>()?;
v.push(b.as_bytes().to_vec());
}
if let Some(b) = ob
.get_item(ob.len()? - 1)?
.extract::<Option<Bound<PyBytes>>>()?
{
v.push(b.as_bytes().to_vec());
Ok(Key::Fixed(v))
} else {
Ok(Key::ContentAddressed(v))
}
}
}
#[cfg(feature = "pyo3")]
impl<'py> pyo3::IntoPyObject<'py> for Key {
type Target = pyo3::types::PyTuple;
type Output = pyo3::Bound<'py, Self::Target>;
type Error = pyo3::PyErr;
fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
use pyo3::types::{PyBytes, PyTuple};
match self {
Key::Fixed(v) => {
let t = PyTuple::new(py, v.into_iter().map(|v| PyBytes::new(py, v.as_slice())));
t
}
Key::ContentAddressed(v) => {
let mut entries = v
.into_iter()
.map(|v| PyBytes::new(py, v.as_slice()).into_any())
.collect::<Vec<_>>();
entries.push(py.None().into_bound(py).into_any());
PyTuple::new(py, entries)
}
}
}
}
#[derive(Debug, Clone)]
pub struct KeyRefs<K: Eq + std::hash::Hash + Clone> {
refs: HashMap<K, std::collections::HashSet<K>>,
new_keys: Option<std::collections::HashSet<K>>,
}
impl<K: Eq + std::hash::Hash + Clone> KeyRefs<K> {
pub fn new(track_new_keys: bool) -> Self {
Self {
refs: HashMap::new(),
new_keys: if track_new_keys {
Some(std::collections::HashSet::new())
} else {
None
},
}
}
pub fn clear(&mut self) {
self.refs.clear();
if let Some(new_keys) = self.new_keys.as_mut() {
new_keys.clear();
}
}
pub fn add_references<I>(&mut self, key: K, refs: I)
where
I: IntoIterator<Item = K>,
{
for referenced in refs {
self.refs.entry(referenced).or_default().insert(key.clone());
}
self.add_key(key);
}
pub fn add_key(&mut self, key: K) {
self.satisfy_refs_for_key(&key);
if let Some(new_keys) = self.new_keys.as_mut() {
new_keys.insert(key);
}
}
pub fn satisfy_refs_for_key(&mut self, key: &K) {
self.refs.remove(key);
}
pub fn satisfy_refs_for_keys<I>(&mut self, keys: I)
where
I: IntoIterator<Item = K>,
{
for key in keys {
self.satisfy_refs_for_key(&key);
}
}
pub fn unsatisfied_refs(&self) -> impl Iterator<Item = &K> {
self.refs.keys()
}
pub fn referrers(&self) -> std::collections::HashSet<K> {
let mut out = std::collections::HashSet::new();
for set in self.refs.values() {
for key in set {
out.insert(key.clone());
}
}
out
}
pub fn new_keys(&self) -> Option<&std::collections::HashSet<K>> {
self.new_keys.as_ref()
}
}
impl<K: Eq + std::hash::Hash + Clone> Default for KeyRefs<K> {
fn default() -> Self {
Self::new(false)
}
}
#[cfg(test)]
mod key_refs_tests {
use super::*;
#[test]
fn add_references_records_referrers() {
let mut refs: KeyRefs<Vec<u8>> = KeyRefs::new(false);
refs.add_references(b"child".to_vec(), vec![b"p1".to_vec(), b"p2".to_vec()]);
let unsatisfied: std::collections::HashSet<Vec<u8>> =
refs.unsatisfied_refs().cloned().collect();
let expected: std::collections::HashSet<Vec<u8>> =
vec![b"p1".to_vec(), b"p2".to_vec()].into_iter().collect();
assert_eq!(unsatisfied, expected);
}
#[test]
fn adding_a_parent_satisfies_references_to_it() {
let mut refs: KeyRefs<Vec<u8>> = KeyRefs::new(false);
refs.add_references(b"child".to_vec(), vec![b"p1".to_vec()]);
refs.add_key(b"p1".to_vec());
assert!(refs.unsatisfied_refs().next().is_none());
}
#[test]
fn new_keys_only_tracked_when_enabled() {
let mut tracking: KeyRefs<Vec<u8>> = KeyRefs::new(true);
let mut untracked: KeyRefs<Vec<u8>> = KeyRefs::new(false);
tracking.add_key(b"k".to_vec());
untracked.add_key(b"k".to_vec());
assert_eq!(tracking.new_keys().map(|s| s.len()), Some(1));
assert!(untracked.new_keys().is_none());
}
#[test]
fn referrers_flattens_duplicate_children() {
let mut refs: KeyRefs<Vec<u8>> = KeyRefs::new(false);
refs.add_references(b"c1".to_vec(), vec![b"p1".to_vec(), b"p2".to_vec()]);
refs.add_references(b"c2".to_vec(), vec![b"p1".to_vec()]);
let referrers = refs.referrers();
let expected: std::collections::HashSet<Vec<u8>> =
vec![b"c1".to_vec(), b"c2".to_vec()].into_iter().collect();
assert_eq!(referrers, expected);
}
#[test]
fn clear_resets_refs_and_new_keys() {
let mut refs: KeyRefs<Vec<u8>> = KeyRefs::new(true);
refs.add_references(b"c".to_vec(), vec![b"p".to_vec()]);
refs.clear();
assert!(refs.unsatisfied_refs().next().is_none());
assert_eq!(refs.new_keys().map(|s| s.len()), Some(0));
}
}
impl bendy::encoding::ToBencode for Key {
const MAX_DEPTH: usize = 10;
fn encode(
&self,
encoder: bendy::encoding::SingleItemEncoder<'_>,
) -> Result<(), bendy::encoding::Error> {
match self {
Key::Fixed(v) => encoder.emit_list(|e| {
for v in v.iter() {
e.emit_bytes(v)?;
}
Ok(())
}),
Key::ContentAddressed(_v) => {
panic!("ContentAddressed keys are not supported in bencode")
}
}
}
}
#[test]
fn test_network_bytes_to_kind_and_offset() {
let (kind, offset) = network_bytes_to_kind_and_offset(b"fulltext\nrest");
assert_eq!(kind, "fulltext");
assert_eq!(offset, 9);
}
#[test]
fn test_key_bencode() {
let x = Key::Fixed(vec![b"foo".to_vec(), b"bar".to_vec()]);
let z = bendy::encoding::ToBencode::to_bencode(&x).unwrap();
assert_eq!(z, b"l3:foo3:bare".to_vec());
}
pub trait ContentFactory {
fn sha1(&self) -> Option<Vec<u8>>;
fn size(&self) -> Option<usize>;
fn key(&self) -> Key;
fn parents(&self) -> Option<Vec<Key>>;
fn to_fulltext<'a, 'b>(&'a self) -> Cow<'b, [u8]>
where
'a: 'b;
fn to_chunks<'a, 'b>(&'a self) -> Box<dyn Iterator<Item = Cow<'b, [u8]>> + 'b>
where
'a: 'b;
fn to_lines<'a, 'b>(&'a self) -> Box<dyn Iterator<Item = Cow<'b, [u8]>> + 'b>
where
'a: 'b;
fn into_fulltext(self) -> Vec<u8>;
fn into_chunks(self) -> Box<dyn Iterator<Item = Vec<u8>>>;
fn into_lines(self) -> Box<dyn Iterator<Item = Vec<u8>>>
where
Self: Sized,
{
Box::new(
crate::osutils::chunks_to_lines(self.into_chunks().map(Ok::<_, std::io::Error>))
.map(|v| v.unwrap().into_owned()),
)
}
fn storage_kind(&self) -> String;
fn map_key(&mut self, f: &dyn Fn(Key) -> Key);
}
pub struct FulltextContentFactory {
sha1: Option<Vec<u8>>,
size: usize,
key: Key,
parents: Option<Vec<Key>>,
fulltext: Vec<u8>,
}
impl ContentFactory for FulltextContentFactory {
fn sha1(&self) -> Option<Vec<u8>> {
self.sha1.clone()
}
fn size(&self) -> Option<usize> {
Some(self.size)
}
fn key(&self) -> Key {
self.key.clone()
}
fn parents(&self) -> Option<Vec<Key>> {
self.parents.clone()
}
fn to_fulltext<'a, 'b>(&'a self) -> Cow<'b, [u8]>
where
'a: 'b,
{
Cow::Borrowed(&self.fulltext)
}
fn to_chunks<'a, 'b>(&'a self) -> Box<dyn Iterator<Item = Cow<'b, [u8]>> + 'b>
where
'a: 'b,
{
Box::new(
self.fulltext
.as_slice()
.chunks(crate::DEFAULT_CHUNK_SIZE)
.map(|v| v.into()),
)
}
fn to_lines<'a, 'b>(&'a self) -> Box<dyn Iterator<Item = Cow<'b, [u8]>> + 'b>
where
'a: 'b,
{
Box::new(
crate::osutils::chunks_to_lines(std::iter::once(Ok::<_, std::io::Error>(
&self.fulltext,
)))
.map(|v| v.unwrap()),
)
}
fn into_fulltext(self) -> Vec<u8> {
self.fulltext
}
fn into_chunks(self) -> Box<dyn Iterator<Item = Vec<u8>>> {
let mut fulltext = self.fulltext;
Box::new(std::iter::from_fn(move || {
if fulltext.is_empty() {
None
} else {
let chunk = fulltext
.drain(..std::cmp::min(crate::DEFAULT_CHUNK_SIZE, fulltext.len()))
.collect::<Vec<_>>();
Some(chunk)
}
}))
}
fn storage_kind(&self) -> String {
"fulltext".into()
}
fn map_key(&mut self, f: &dyn Fn(Key) -> Key) {
self.key = f(self.key.clone());
self.parents = self.parents.take().map(|v| v.into_iter().map(f).collect());
}
}
impl FulltextContentFactory {
pub fn new(
sha1: Option<Vec<u8>>,
key: Key,
parents: Option<Vec<Key>>,
fulltext: Vec<u8>,
) -> Self {
Self {
sha1,
size: fulltext.len(),
key,
parents,
fulltext,
}
}
}
pub struct ChunkedContentFactory {
sha1: Option<Vec<u8>>,
size: usize,
key: Key,
parents: Option<Vec<Key>>,
chunks: Vec<Vec<u8>>,
}
impl ChunkedContentFactory {
pub fn new(
sha1: Option<Vec<u8>>,
key: Key,
parents: Option<Vec<Key>>,
chunks: Vec<Vec<u8>>,
) -> Self {
Self {
sha1,
size: chunks.iter().map(|v| v.len()).sum(),
key,
parents,
chunks,
}
}
}
impl ContentFactory for ChunkedContentFactory {
fn sha1(&self) -> Option<Vec<u8>> {
self.sha1.clone()
}
fn size(&self) -> Option<usize> {
Some(self.size)
}
fn key(&self) -> Key {
self.key.clone()
}
fn parents(&self) -> Option<Vec<Key>> {
self.parents.clone()
}
fn to_fulltext<'a, 'b>(&'a self) -> Cow<'b, [u8]>
where
'a: 'b,
{
self.chunks.concat().into()
}
fn to_chunks<'a, 'b>(&'a self) -> Box<dyn Iterator<Item = Cow<'b, [u8]>> + 'b>
where
'a: 'b,
{
Box::new(self.chunks.iter().map(|v| v.into()))
}
fn to_lines<'a, 'b>(&'a self) -> Box<dyn Iterator<Item = Cow<'b, [u8]>> + 'b>
where
'a: 'b,
{
Box::new(
crate::osutils::chunks_to_lines(self.chunks.iter().map(Ok::<_, std::io::Error>))
.map(|l| l.unwrap()),
)
}
fn into_fulltext(self) -> Vec<u8> {
self.chunks.into_iter().flatten().collect()
}
fn into_chunks(self) -> Box<dyn Iterator<Item = Vec<u8>>> {
Box::new(self.chunks.into_iter())
}
fn storage_kind(&self) -> String {
"chunked".into()
}
fn map_key(&mut self, f: &dyn Fn(Key) -> Key) {
self.key = f(self.key.clone());
self.parents = self.parents.take().map(|v| v.into_iter().map(f).collect());
}
}
pub struct AbsentContentFactory {
key: Key,
}
impl AbsentContentFactory {
pub fn new(key: Key) -> Self {
Self { key }
}
}
impl ContentFactory for AbsentContentFactory {
fn sha1(&self) -> Option<Vec<u8>> {
None
}
fn size(&self) -> Option<usize> {
None
}
fn key(&self) -> Key {
self.key.clone()
}
fn parents(&self) -> Option<Vec<Key>> {
None
}
fn to_fulltext<'a, 'b>(&'a self) -> Cow<'b, [u8]>
where
'a: 'b,
{
panic!("A request was made for key: {}, but that content is not available, and the calling code does not handle if it is missing.", self.key);
}
fn to_chunks<'a, 'b>(&'a self) -> Box<dyn Iterator<Item = Cow<'b, [u8]>> + 'b>
where
'a: 'b,
{
panic!("A request was made for key: {}, but that content is not available, and the calling code does not handle if it is missing.", self.key);
}
fn to_lines<'a, 'b>(&'a self) -> Box<dyn Iterator<Item = Cow<'b, [u8]>> + 'b>
where
'a: 'b,
{
panic!("A request was made for key: {}, but that content is not available, and the calling code does not handle if it is missing.", self.key);
}
fn into_fulltext(self) -> Vec<u8> {
panic!("A request was made for key: {}, but that content is not available, and the calling code does not handle if it is missing.", self.key);
}
fn into_chunks(self) -> Box<dyn Iterator<Item = Vec<u8>>> {
panic!("A request was made for key: {}, but that content is not available, and the calling code does not handle if it is missing.", self.key);
}
fn storage_kind(&self) -> String {
"absent".into()
}
fn map_key(&mut self, f: &dyn Fn(Key) -> Key) {
self.key = f(self.key.clone());
}
}
pub trait VersionedFile<CF: ContentFactory, I> {
fn check_not_reserved_id(id: &VersionId) -> bool;
fn get_record_stream(
&self,
keys: &[&VersionId],
ordering: Ordering,
include_delta_closure: bool,
) -> Box<dyn Iterator<Item = CF>>;
fn add_lines<'a>(
&mut self,
version_id: &VersionId,
parent_texts: Option<HashMap<VersionId, I>>,
lines: impl Iterator<Item = &'a [u8]>,
nostore_sha: Option<bool>,
random_id: bool,
) -> Result<(Vec<u8>, usize, I), Error>;
fn has_version(&self, version_id: &VersionId) -> bool;
fn insert_record_stream(
&mut self,
stream: impl Iterator<Item = Box<dyn ContentFactory>>,
) -> Result<(), Error>;
fn get_format_signature(&self) -> String;
fn get_lines(
&self,
version_id: &VersionId,
) -> Result<Box<dyn Iterator<Item = Vec<u8>>>, Error> {
let record_stream = self.get_record_stream(&[version_id], Ordering::Unordered, false);
if let Some(record) = record_stream.into_iter().next() {
Ok(record.into_lines())
} else {
Err(Error::VersionNotPresent(version_id.clone()))
}
}
fn get_text(&self, version_id: &VersionId) -> Result<Vec<u8>, Error> {
let record_stream = self.get_record_stream(&[version_id], Ordering::Unordered, false);
if let Some(record) = record_stream.into_iter().next() {
Ok(record.into_fulltext())
} else {
Err(Error::VersionNotPresent(version_id.clone()))
}
}
fn get_chunks(
&self,
version_id: &VersionId,
) -> Result<Box<dyn Iterator<Item = Vec<u8>>>, Error> {
let record_stream = self.get_record_stream(&[version_id], Ordering::Unordered, false);
if let Some(record) = record_stream.into_iter().next() {
Ok(record.into_chunks())
} else {
Err(Error::VersionNotPresent(version_id.clone()))
}
}
}
pub trait VersionedFiles: Send + Sync {
fn get_parent_map(
&self,
keys: &[Key],
) -> Result<HashMap<Key, Vec<Key>>, crate::knit::KnitError>;
fn get_record_stream(
&self,
keys: &[Key],
ordering: &str,
include_delta_closure: bool,
) -> Result<
Box<dyn Iterator<Item = Result<Box<dyn ContentFactory>, crate::knit::KnitError>>>,
crate::knit::KnitError,
>;
fn get_sha1s(&self, keys: &[Key]) -> Result<HashMap<Key, Vec<u8>>, crate::knit::KnitError>;
fn keys(&self) -> Result<Vec<Key>, crate::knit::KnitError>;
fn add_lines(
&self,
key: &Key,
parents: Option<&[Key]>,
lines: &[Vec<u8>],
) -> Result<(Vec<u8>, usize), crate::knit::KnitError>;
fn insert_record_stream(
&self,
stream: Box<dyn Iterator<Item = Box<dyn ContentFactory>>>,
) -> Result<(), crate::knit::KnitError>;
fn iter_lines_added_or_present_in_keys(
&self,
keys: &[Key],
) -> Result<Vec<(Vec<u8>, Key)>, crate::knit::KnitError>;
fn annotate(&self, key: &Key) -> Result<Vec<(Key, Vec<u8>)>, crate::knit::KnitError>;
fn get_missing_compression_parent_keys(&self) -> Result<Vec<Key>, crate::knit::KnitError> {
Err(crate::knit::KnitError::NotImplemented(
"get_missing_compression_parent_keys",
))
}
fn clear_cache(&self) {}
fn check(&self) -> Result<(), crate::knit::KnitError> {
Err(crate::knit::KnitError::NotImplemented("check"))
}
fn known_graph_ancestry_map(
&self,
keys: &[Key],
) -> Result<HashMap<Key, Vec<Key>>, crate::knit::KnitError> {
let mut parent_map: HashMap<Key, Vec<Key>> = HashMap::new();
let mut pending: Vec<Key> = keys.to_vec();
while !pending.is_empty() {
let this = self.get_parent_map(&pending)?;
let mut next: Vec<Key> = Vec::new();
for parents in this.values() {
for p in parents {
if !parent_map.contains_key(p) && !this.contains_key(p) {
next.push(p.clone());
}
}
}
for (k, v) in this {
parent_map.insert(k, v);
}
next.sort();
next.dedup();
pending = next;
}
Ok(parent_map)
}
}
impl VersionedFiles for std::sync::Arc<dyn VersionedFiles + Send + Sync> {
fn get_parent_map(
&self,
keys: &[Key],
) -> Result<HashMap<Key, Vec<Key>>, crate::knit::KnitError> {
(**self).get_parent_map(keys)
}
fn get_record_stream(
&self,
keys: &[Key],
ordering: &str,
include_delta_closure: bool,
) -> Result<
Box<dyn Iterator<Item = Result<Box<dyn ContentFactory>, crate::knit::KnitError>>>,
crate::knit::KnitError,
> {
(**self).get_record_stream(keys, ordering, include_delta_closure)
}
fn get_sha1s(&self, keys: &[Key]) -> Result<HashMap<Key, Vec<u8>>, crate::knit::KnitError> {
(**self).get_sha1s(keys)
}
fn keys(&self) -> Result<Vec<Key>, crate::knit::KnitError> {
(**self).keys()
}
fn add_lines(
&self,
key: &Key,
parents: Option<&[Key]>,
lines: &[Vec<u8>],
) -> Result<(Vec<u8>, usize), crate::knit::KnitError> {
(**self).add_lines(key, parents, lines)
}
fn insert_record_stream(
&self,
stream: Box<dyn Iterator<Item = Box<dyn ContentFactory>>>,
) -> Result<(), crate::knit::KnitError> {
(**self).insert_record_stream(stream)
}
fn iter_lines_added_or_present_in_keys(
&self,
keys: &[Key],
) -> Result<Vec<(Vec<u8>, Key)>, crate::knit::KnitError> {
(**self).iter_lines_added_or_present_in_keys(keys)
}
fn annotate(&self, key: &Key) -> Result<Vec<(Key, Vec<u8>)>, crate::knit::KnitError> {
(**self).annotate(key)
}
fn get_missing_compression_parent_keys(&self) -> Result<Vec<Key>, crate::knit::KnitError> {
(**self).get_missing_compression_parent_keys()
}
fn clear_cache(&self) {
(**self).clear_cache()
}
fn check(&self) -> Result<(), crate::knit::KnitError> {
(**self).check()
}
}
pub struct VirtualVersionedFiles<GP, GL>
where
GP: Fn(&[Vec<u8>]) -> Result<HashMap<Vec<u8>, Vec<Vec<u8>>>, crate::knit::KnitError>
+ Send
+ Sync,
GL: Fn(&[u8]) -> Result<Option<Vec<Vec<u8>>>, crate::knit::KnitError> + Send + Sync,
{
get_parent_map_cb: GP,
get_lines_cb: GL,
}
impl<GP, GL> VirtualVersionedFiles<GP, GL>
where
GP: Fn(&[Vec<u8>]) -> Result<HashMap<Vec<u8>, Vec<Vec<u8>>>, crate::knit::KnitError>
+ Send
+ Sync,
GL: Fn(&[u8]) -> Result<Option<Vec<Vec<u8>>>, crate::knit::KnitError> + Send + Sync,
{
pub fn new(get_parent_map_cb: GP, get_lines_cb: GL) -> Self {
Self {
get_parent_map_cb,
get_lines_cb,
}
}
}
fn key_to_single_bytes(key: &Key) -> Result<&[u8], crate::knit::KnitError> {
let segs = match key {
Key::Fixed(v) | Key::ContentAddressed(v) => v,
};
if segs.len() != 1 {
return Err(crate::knit::KnitError::Corrupt(format!(
"VirtualVersionedFiles expects single-segment keys, got {:?}",
key
)));
}
Ok(&segs[0])
}
impl<GP, GL> VersionedFiles for VirtualVersionedFiles<GP, GL>
where
GP: Fn(&[Vec<u8>]) -> Result<HashMap<Vec<u8>, Vec<Vec<u8>>>, crate::knit::KnitError>
+ Send
+ Sync,
GL: Fn(&[u8]) -> Result<Option<Vec<Vec<u8>>>, crate::knit::KnitError> + Send + Sync,
{
fn get_parent_map(
&self,
keys: &[Key],
) -> Result<HashMap<Key, Vec<Key>>, crate::knit::KnitError> {
let mut bare: Vec<Vec<u8>> = Vec::with_capacity(keys.len());
for k in keys {
bare.push(key_to_single_bytes(k)?.to_vec());
}
let raw = (self.get_parent_map_cb)(&bare)?;
let mut out: HashMap<Key, Vec<Key>> = HashMap::with_capacity(raw.len());
for (k, parents) in raw {
let key = Key::Fixed(vec![k]);
let py_parents = parents
.into_iter()
.map(|p| Key::Fixed(vec![p]))
.collect::<Vec<_>>();
out.insert(key, py_parents);
}
Ok(out)
}
fn get_record_stream(
&self,
keys: &[Key],
_ordering: &str,
_include_delta_closure: bool,
) -> Result<
Box<dyn Iterator<Item = Result<Box<dyn ContentFactory>, crate::knit::KnitError>>>,
crate::knit::KnitError,
> {
let mut records: Vec<Result<Box<dyn ContentFactory>, crate::knit::KnitError>> = Vec::new();
for key in keys {
let bare = key_to_single_bytes(key)?.to_vec();
match (self.get_lines_cb)(&bare) {
Ok(Some(lines)) => {
let sha = crate::weave::sha_strings(&lines);
let factory =
ChunkedContentFactory::new(Some(sha), Key::Fixed(vec![bare]), None, lines);
records.push(Ok(Box::new(factory) as Box<dyn ContentFactory>));
}
Ok(None) => {
let factory = AbsentContentFactory::new(Key::Fixed(vec![bare]));
records.push(Ok(Box::new(factory) as Box<dyn ContentFactory>));
}
Err(e) => records.push(Err(e)),
}
}
Ok(Box::new(records.into_iter()))
}
fn get_sha1s(&self, keys: &[Key]) -> Result<HashMap<Key, Vec<u8>>, crate::knit::KnitError> {
let mut out = HashMap::new();
for key in keys {
let bare = key_to_single_bytes(key)?;
if let Some(lines) = (self.get_lines_cb)(bare)? {
out.insert(key.clone(), crate::weave::sha_strings(&lines));
}
}
Ok(out)
}
fn keys(&self) -> Result<Vec<Key>, crate::knit::KnitError> {
Err(crate::knit::KnitError::NotImplemented(
"VirtualVersionedFiles.keys",
))
}
fn add_lines(
&self,
_key: &Key,
_parents: Option<&[Key]>,
_lines: &[Vec<u8>],
) -> Result<(Vec<u8>, usize), crate::knit::KnitError> {
Err(crate::knit::KnitError::NotImplemented(
"VirtualVersionedFiles.add_lines",
))
}
fn insert_record_stream(
&self,
_stream: Box<dyn Iterator<Item = Box<dyn ContentFactory>>>,
) -> Result<(), crate::knit::KnitError> {
Err(crate::knit::KnitError::NotImplemented(
"VirtualVersionedFiles.insert_record_stream",
))
}
fn iter_lines_added_or_present_in_keys(
&self,
keys: &[Key],
) -> Result<Vec<(Vec<u8>, Key)>, crate::knit::KnitError> {
let mut out = Vec::new();
for key in keys {
let bare = key_to_single_bytes(key)?;
if let Some(lines) = (self.get_lines_cb)(bare)? {
for line in lines {
out.push((line, key.clone()));
}
}
}
Ok(out)
}
fn annotate(&self, _key: &Key) -> Result<Vec<(Key, Vec<u8>)>, crate::knit::KnitError> {
Err(crate::knit::KnitError::NotImplemented(
"VirtualVersionedFiles.annotate",
))
}
fn check(&self) -> Result<(), crate::knit::KnitError> {
Ok(())
}
}
pub fn check_lines_are_lines(lines: &[Vec<u8>]) -> bool {
lines.iter().all(|line| {
let body = line.split_last().map_or(&[][..], |(_, rest)| rest);
!body.contains(&b'\n')
})
}
pub fn write_fulltext_record<W: std::io::Write>(
key: &Key,
parents: Option<&[Key]>,
fulltext: &[u8],
w: &mut W,
) -> std::io::Result<()> {
let mut record_meta = bendy::encoding::Encoder::new();
record_meta
.emit_list(|e| {
e.emit(key)?;
if let Some(parents) = parents {
e.emit_list(|e| {
for parent in parents {
e.emit(parent)?;
}
Ok(())
})?;
} else {
e.emit_bytes(&b"nil"[..])?; }
Ok(())
})
.unwrap();
let record_meta = record_meta.get_output().unwrap();
w.write_all(b"fulltext\n")?;
w.write_all(&length_prefix(&record_meta))?;
w.write_all(&record_meta)?;
w.write_all(fulltext)?;
Ok(())
}
pub fn record_to_fulltext_bytes<R: ContentFactory, W: std::io::Write>(
record: R,
w: &mut W,
) -> std::io::Result<()> {
let key = record.key();
let parents = record.parents();
write_fulltext_record(&key, parents.as_deref(), &record.into_fulltext(), w)
}
fn length_prefix(data: &[u8]) -> Vec<u8> {
let length = data.len() as u32;
let mut length_bytes = vec![];
length_bytes
.write_u32::<BigEndian>(length)
.expect("Failed to write length bytes");
length_bytes
}
pub fn network_bytes_to_kind_and_offset(network_bytes: &[u8]) -> (String, usize) {
let line_end = network_bytes
.iter()
.position(|&b| b == b'\n')
.expect("network bytes must contain a newline-terminated kind");
let storage_kind =
std::str::from_utf8(&network_bytes[..line_end]).expect("storage kind must be ASCII");
(storage_kind.to_string(), line_end + 1)
}
pub fn fulltext_network_to_record(bytes: &[u8], line_end: usize) -> FulltextContentFactory {
let meta_len_bytes: [u8; 4] = bytes[line_end..line_end + 4]
.try_into()
.expect("Expected 4 bytes for meta_len");
let meta_len = u32::from_be_bytes(meta_len_bytes) as usize;
let record_meta = &bytes[line_end + 4..line_end + 4 + meta_len];
let mut decoder = bendy::decoding::Decoder::new(record_meta);
let mut tuple = decoder
.next_object()
.expect("Failed to decode record_meta using Bencode")
.expect("Failed to decode tuple using Bencode")
.try_into_list()
.unwrap();
fn decode_key(o: bendy::decoding::Object) -> Key {
let mut ret = vec![];
let mut l = o.try_into_list().unwrap();
while let Some(b) = l.next_object().unwrap() {
ret.push(b.try_into_bytes().unwrap().to_vec());
}
Key::Fixed(ret)
}
let key = decode_key(
tuple
.next_object()
.expect("Failed to decode record_meta using Bencode")
.expect("Failed to decode key using Bencode"),
);
let parents = tuple
.next_object()
.expect("Failed to decode record_meta using Bencode")
.expect("Failed to decode parents using Bencode");
let parents = match parents {
bendy::decoding::Object::Bytes(bytes) => {
if bytes == b"nil" {
None
} else {
panic!("Expected parents to be a list or nil");
}
}
bendy::decoding::Object::List(mut l) => {
let mut parents = vec![];
while let Some(parent) = l.next_object().unwrap() {
parents.push(decode_key(parent));
}
Some(parents)
}
_ => panic!("Expected parents to be a list or nil"),
};
let fulltext = &bytes[line_end + 4 + meta_len..];
FulltextContentFactory::new(None, key, parents, fulltext.to_vec())
}
#[derive(Clone, Debug)]
pub struct MpdiffRecord {
pub key: Key,
pub parents: Vec<Key>,
pub expected_sha1: Vec<u8>,
pub diff: crate::multiparent::MultiParent,
}
#[derive(Clone, Debug)]
pub struct PreparedAddLines {
pub key: Key,
pub parents: Vec<Key>,
pub lines: Vec<Vec<u8>>,
pub left_matching_blocks: Option<Vec<(usize, usize, usize)>>,
pub expected_sha1: Vec<u8>,
}
pub fn add_mpdiffs_build(
records: &[MpdiffRecord],
) -> (crate::multiparent::MultiMemoryVersionedFile<Key>, Vec<Key>) {
let mut mpvf = crate::multiparent::MultiMemoryVersionedFile::<Key>::default();
for r in records {
mpvf.add_diff(r.diff.clone(), r.key.clone(), r.parents.clone());
}
let mut needed: std::collections::HashSet<Key> = std::collections::HashSet::new();
for r in records {
for p in &r.parents {
if !mpvf.has_version(p) {
needed.insert(p.clone());
}
}
}
(mpvf, needed.into_iter().collect())
}
pub fn add_mpdiffs_prepare(
mpvf: &mut crate::multiparent::MultiMemoryVersionedFile<Key>,
records: &[MpdiffRecord],
) -> Result<Vec<PreparedAddLines>, crate::multiparent::ReconstructError> {
let keys: Vec<Key> = records.iter().map(|r| r.key.clone()).collect();
let reconstructed = mpvf.get_line_list(&keys)?;
Ok(records
.iter()
.zip(reconstructed.into_iter())
.map(|(r, lines)| {
let left_matching_blocks = if r.parents.len() == 1 {
let parent_len = mpvf
.get_diff(&r.parents[0])
.map(crate::multiparent::MultiParent::num_lines)
.unwrap_or(0);
Some(r.diff.get_matching_blocks(0, parent_len))
} else {
None
};
PreparedAddLines {
key: r.key.clone(),
parents: r.parents.clone(),
lines,
left_matching_blocks,
expected_sha1: r.expected_sha1.clone(),
}
})
.collect())
}
pub fn make_mpdiffs(
vf: &dyn VersionedFiles,
ordered_keys: &[Key],
) -> Result<Vec<crate::multiparent::MultiParent>, crate::knit::KnitError> {
let parent_map = vf.get_parent_map(ordered_keys)?;
let mut missing_keys: Vec<Key> = Vec::new();
for k in ordered_keys {
if !parent_map.contains_key(k) {
missing_keys.push(k.clone());
}
}
if let Some(first) = missing_keys.into_iter().next() {
return Err(crate::knit::KnitError::RevisionNotPresent(
first.segments().to_vec(),
));
}
let mut needed_keys: std::collections::HashSet<Key> = ordered_keys.iter().cloned().collect();
let mut refcounts: std::collections::HashMap<Key, usize> = std::collections::HashMap::new();
let mut just_parents: std::collections::HashSet<Key> = std::collections::HashSet::new();
for parents in parent_map.values() {
if parents.is_empty() {
continue;
}
for p in parents {
just_parents.insert(p.clone());
needed_keys.insert(p.clone());
*refcounts.entry(p.clone()).or_insert(0) += 1;
}
}
just_parents.retain(|p| !parent_map.contains_key(p));
let just_parents_vec: Vec<Key> = just_parents.iter().cloned().collect();
let present_parents = vf.get_parent_map(&just_parents_vec)?;
let ghost_parents: std::collections::HashSet<Key> = just_parents
.iter()
.filter(|p| !present_parents.contains_key(*p))
.cloned()
.collect();
for g in &ghost_parents {
needed_keys.remove(g);
}
let mut parent_map = parent_map;
let mut chunks_cache: std::collections::HashMap<Key, Vec<Vec<u8>>> =
std::collections::HashMap::new();
let mut diffs: std::collections::HashMap<Key, crate::multiparent::MultiParent> =
std::collections::HashMap::new();
let needed_keys_vec: Vec<Key> = needed_keys.into_iter().collect();
let stream = vf.get_record_stream(&needed_keys_vec, "topological", true)?;
for rec in stream {
let rec = rec?;
if rec.storage_kind() == "absent" {
return Err(crate::knit::KnitError::RevisionNotPresent(
rec.key().segments().to_vec(),
));
}
let key = rec.key();
let this_lines: Vec<Vec<u8>> = rec.to_lines().map(|c| c.into_owned()).collect();
let cache_this = refcounts.contains_key(&key);
if let Some(parents) = parent_map.remove(&key) {
let mut parent_lines: Vec<Vec<Vec<u8>>> = Vec::with_capacity(parents.len());
for p in &parents {
if ghost_parents.contains(p) {
continue;
}
let count = refcounts.get_mut(p).ok_or_else(|| {
crate::knit::KnitError::Corrupt(format!(
"make_mpdiffs: missing refcount for {:?}",
p
))
})?;
if *count == 1 {
refcounts.remove(p);
let cached = chunks_cache.remove(p).ok_or_else(|| {
crate::knit::KnitError::Corrupt(format!(
"make_mpdiffs: missing cached chunks for {:?}",
p
))
})?;
parent_lines.push(cached);
} else {
*count -= 1;
let cached = chunks_cache.get(p).cloned().ok_or_else(|| {
crate::knit::KnitError::Corrupt(format!(
"make_mpdiffs: missing cached chunks for {:?}",
p
))
})?;
parent_lines.push(cached);
}
}
let parent_refs: Vec<&[Vec<u8>]> = parent_lines.iter().map(Vec::as_slice).collect();
let diff = crate::multiparent::MultiParent::from_lines(&this_lines, &parent_refs, None);
diffs.insert(key.clone(), diff);
}
if cache_this {
chunks_cache.insert(key, this_lines);
}
}
let mut out: Vec<crate::multiparent::MultiParent> = Vec::with_capacity(ordered_keys.len());
for k in ordered_keys {
match diffs.remove(k) {
Some(d) => out.push(d),
None => {
return Err(crate::knit::KnitError::Corrupt(format!(
"make_mpdiffs: never produced a diff for {:?}",
k
)));
}
}
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn check_lines_are_lines_accepts_proper_lines() {
assert!(check_lines_are_lines(&[]));
assert!(check_lines_are_lines(&[b"a\n".to_vec(), b"b\n".to_vec()]));
assert!(check_lines_are_lines(&[
b"a\n".to_vec(),
b"no-eol".to_vec()
]));
assert!(check_lines_are_lines(&[b"\n".to_vec()]));
assert!(check_lines_are_lines(&[b"".to_vec()]));
}
#[test]
fn check_lines_are_lines_rejects_embedded_newlines() {
assert!(!check_lines_are_lines(&[b"a\nb\n".to_vec()]));
assert!(!check_lines_are_lines(&[
b"ok\n".to_vec(),
b"a\nb".to_vec()
]));
}
fn k(s: &str) -> Key {
Key::Fixed(vec![s.as_bytes().to_vec()])
}
#[test]
fn add_mpdiffs_build_collects_only_missing_parents() {
use crate::multiparent::{Hunk, MultiParent};
let snap_a = MultiParent::with_hunks(vec![Hunk::NewText(vec![b"a\n".to_vec()])]);
let snap_b = MultiParent::with_hunks(vec![Hunk::NewText(vec![b"b\n".to_vec()])]);
let records = vec![
MpdiffRecord {
key: k("a"),
parents: vec![],
expected_sha1: vec![],
diff: snap_a,
},
MpdiffRecord {
key: k("b"),
parents: vec![k("a"), k("z")],
expected_sha1: vec![],
diff: snap_b,
},
];
let (mpvf, needed) = add_mpdiffs_build(&records);
assert_eq!(needed, vec![k("z")]);
assert!(mpvf.has_version(&k("a")));
assert!(mpvf.has_version(&k("b")));
}
#[test]
fn add_mpdiffs_prepare_single_parent_emits_blocks() {
use crate::multiparent::{Hunk, MultiParent};
let snap_a =
MultiParent::with_hunks(vec![Hunk::NewText(vec![b"x\n".to_vec(), b"y\n".to_vec()])]);
let delta_b = MultiParent::with_hunks(vec![Hunk::ParentText {
parent: 0,
parent_pos: 0,
child_pos: 0,
num_lines: 2,
}]);
let records = vec![
MpdiffRecord {
key: k("a"),
parents: vec![],
expected_sha1: vec![],
diff: snap_a,
},
MpdiffRecord {
key: k("b"),
parents: vec![k("a")],
expected_sha1: vec![],
diff: delta_b,
},
];
let (mut mpvf, needed) = add_mpdiffs_build(&records);
assert!(needed.is_empty());
let prepared = add_mpdiffs_prepare(&mut mpvf, &records).unwrap();
assert_eq!(prepared.len(), 2);
assert_eq!(prepared[0].lines, vec![b"x\n".to_vec(), b"y\n".to_vec()]);
assert_eq!(prepared[0].left_matching_blocks, None);
assert_eq!(prepared[1].lines, vec![b"x\n".to_vec(), b"y\n".to_vec()]);
let blocks = prepared[1].left_matching_blocks.as_ref().unwrap();
assert_eq!(blocks, &vec![(0, 0, 2), (2, 2, 0)]);
}
#[test]
fn add_mpdiffs_prepare_multi_parent_no_blocks() {
use crate::multiparent::{Hunk, MultiParent};
let snap = || MultiParent::with_hunks(vec![Hunk::NewText(vec![b"x\n".to_vec()])]);
let records = vec![
MpdiffRecord {
key: k("p1"),
parents: vec![],
expected_sha1: vec![],
diff: snap(),
},
MpdiffRecord {
key: k("p2"),
parents: vec![],
expected_sha1: vec![],
diff: snap(),
},
MpdiffRecord {
key: k("c"),
parents: vec![k("p1"), k("p2")],
expected_sha1: vec![],
diff: snap(),
},
];
let (mut mpvf, _) = add_mpdiffs_build(&records);
let prepared = add_mpdiffs_prepare(&mut mpvf, &records).unwrap();
assert_eq!(prepared[2].left_matching_blocks, None);
}
}