#![allow(missing_docs)]
use crate::error::Error;
use crate::graph::{Key, KnownGraph};
use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use pyo3::types::{PyBytes, PyDict, PyIterator, PyList, PyTuple};
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct FulltextContentFactory {
pub sha1: Option<String>,
pub storage_kind: String,
pub key: Key,
pub parents: Option<Vec<Key>>,
}
impl FulltextContentFactory {
pub fn new(
sha1: Option<String>,
storage_kind: String,
key: Key,
parents: Option<Vec<Key>>,
) -> Self {
Self {
sha1,
storage_kind,
key,
parents,
}
}
}
impl<'py> IntoPyObject<'py> for FulltextContentFactory {
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
let factory_mod = py.import("breezy.bzr.versionedfile")?;
let factory_cls = factory_mod.getattr("FulltextContentFactory")?;
let parents_py = if let Some(parents) = self.parents {
let parent_tuples: Vec<_> = parents
.into_iter()
.map(|p| p.into_pyobject(py))
.collect::<Result<Vec<_>, _>>()?;
Some(PyTuple::new(py, parent_tuples)?)
} else {
None
};
let kwargs = PyDict::new(py);
if let Some(sha1) = self.sha1 {
kwargs.set_item("sha1", PyBytes::new(py, sha1.as_bytes()))?;
}
kwargs.set_item("storage_kind", self.storage_kind)?;
kwargs.set_item("key", self.key.into_pyobject(py)?)?;
if let Some(parents) = parents_py {
kwargs.set_item("parents", parents)?;
}
factory_cls.call((), Some(&kwargs))
}
}
#[derive(Debug, Clone)]
pub struct AbsentContentFactory {
pub key: Key,
pub parents: Vec<Key>,
}
impl AbsentContentFactory {
pub fn new(key: Key, parents: Vec<Key>) -> Self {
Self { key, parents }
}
}
impl<'py> IntoPyObject<'py> for AbsentContentFactory {
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
let factory_mod = py.import("breezy.bzr.versionedfile")?;
let factory_cls = factory_mod.getattr("AbsentContentFactory")?;
let parent_tuples: Vec<_> = self
.parents
.into_iter()
.map(|p| p.into_pyobject(py))
.collect::<Result<Vec<_>, _>>()?;
let parents_py = PyTuple::new(py, parent_tuples)?;
factory_cls.call1((self.key.into_pyobject(py)?, parents_py))
}
}
pub trait VersionedFiles: Clone + Send + Sync {
fn add_lines(
&self,
key: &Key,
parents: &[Key],
lines: Vec<&str>,
parent_texts: Option<HashMap<Key, Vec<String>>>,
left_matching_blocks: Option<Vec<(usize, usize, usize)>>,
nostore_sha: Option<&str>,
random_id: bool,
check_content: bool,
) -> Result<(String, usize), Error>;
fn get_record_stream(
&self,
keys: Vec<Key>,
ordering: RecordOrdering,
include_delta_closure: bool,
) -> Result<RecordStream, Error>;
fn get_sha1s(&self, keys: Vec<Key>) -> Result<HashMap<Key, String>, Error>;
fn insert_record_stream(&self, stream: RecordStream) -> Result<(), Error>;
fn keys(&self) -> Result<Vec<Key>, Error>;
fn make_mpdiffs(&self, keys: Vec<Key>) -> Result<Vec<MultiParentDiff>, Error>;
fn get_parent_map(&self, keys: Vec<Key>) -> Result<HashMap<Key, Vec<Key>>, Error>;
fn get_known_graph_ancestry(&self, keys: Vec<Key>) -> Result<KnownGraph, Error>;
fn get_record_stream_for_keys(
&self,
keys: Vec<Key>,
ordering: RecordOrdering,
) -> Result<RecordStream, Error> {
self.get_record_stream(keys, ordering, false)
}
fn has_key(&self, key: &Key) -> Result<bool, Error> {
let parent_map = self.get_parent_map(vec![key.clone()])?;
Ok(parent_map.contains_key(key))
}
}
pub trait PyVersionedFiles: VersionedFiles {
fn to_object(&self, py: Python) -> Py<PyAny>;
}
impl<T: PyVersionedFiles> VersionedFiles for T {
fn add_lines(
&self,
key: &Key,
parents: &[Key],
lines: Vec<&str>,
parent_texts: Option<HashMap<Key, Vec<String>>>,
left_matching_blocks: Option<Vec<(usize, usize, usize)>>,
nostore_sha: Option<&str>,
random_id: bool,
check_content: bool,
) -> Result<(String, usize), Error> {
Python::attach(|py| {
let parents_py: Vec<_> = parents
.iter()
.map(|p| p.clone().into_pyobject(py))
.collect::<Result<Vec<_>, _>>()?;
let parents_tuple = PyTuple::new(py, parents_py)?;
let lines_py = PyList::new(py, lines)?;
let kwargs = PyDict::new(py);
if let Some(parent_texts) = parent_texts {
let parent_texts_dict = PyDict::new(py);
for (k, v) in parent_texts {
let lines_list = PyList::new(py, v)?;
parent_texts_dict.set_item(k.into_pyobject(py)?, lines_list)?;
}
kwargs.set_item("parent_texts", parent_texts_dict)?;
}
if let Some(blocks) = left_matching_blocks {
let blocks_list = PyList::new(py, blocks.iter().map(|(a, b, c)| (*a, *b, *c)))?;
kwargs.set_item("left_matching_blocks", blocks_list)?;
}
if let Some(sha) = nostore_sha {
kwargs.set_item("nostore_sha", PyBytes::new(py, sha.as_bytes()))?;
}
kwargs.set_item("random_id", random_id)?;
kwargs.set_item("check_content", check_content)?;
let result = self.to_object(py).call_method(
py,
"add_lines",
(key.clone().into_pyobject(py)?, parents_tuple, lines_py),
Some(&kwargs),
)?;
let tuple = result
.cast_bound::<PyTuple>(py)
.map_err(|_| PyValueError::new_err("Expected tuple"))?;
let item0 = tuple.get_item(0)?;
let sha1_bytes = item0
.cast::<PyBytes>()
.map_err(|_| PyValueError::new_err("Expected bytes"))?;
let sha1 = std::str::from_utf8(sha1_bytes.as_bytes())
.map_err(|_| PyValueError::new_err("Invalid UTF-8 in SHA1"))?
.to_string();
let length = tuple.get_item(1)?.extract::<usize>()?;
Ok((sha1, length))
})
}
fn get_record_stream(
&self,
keys: Vec<Key>,
ordering: RecordOrdering,
include_delta_closure: bool,
) -> Result<RecordStream, Error> {
Python::attach(|py| {
let keys_py: Vec<_> = keys
.into_iter()
.map(|k| k.into_pyobject(py))
.collect::<Result<Vec<_>, _>>()?;
let keys_list = PyList::new(py, keys_py)?;
let ordering_str = match ordering {
RecordOrdering::Unordered => "unordered",
RecordOrdering::Topological => "topological",
RecordOrdering::GroupedByKey => "groupcompress",
};
let stream_obj = self.to_object(py).call_method1(
py,
"get_record_stream",
(keys_list, ordering_str, include_delta_closure),
)?;
Ok(RecordStream(stream_obj))
})
}
fn get_sha1s(&self, keys: Vec<Key>) -> Result<HashMap<Key, String>, Error> {
Python::attach(|py| {
let keys_py: Vec<_> = keys
.into_iter()
.map(|k| k.into_pyobject(py))
.collect::<Result<Vec<_>, _>>()?;
let keys_list = PyList::new(py, keys_py)?;
let result_dict = self
.to_object(py)
.call_method1(py, "get_sha1s", (keys_list,))?;
let dict = result_dict
.cast_bound::<PyDict>(py)
.map_err(|_| PyValueError::new_err("Expected dict"))?;
let mut sha1s = HashMap::new();
for (key_py, sha_py) in dict {
let key = key_py.extract::<Key>()?;
let sha_bytes = sha_py
.cast::<PyBytes>()
.map_err(|_| PyValueError::new_err("Expected bytes"))?;
let sha = std::str::from_utf8(sha_bytes.as_bytes())
.map_err(|_| PyValueError::new_err("Invalid UTF-8 in SHA1"))?
.to_string();
sha1s.insert(key, sha);
}
Ok(sha1s)
})
}
fn insert_record_stream(&self, stream: RecordStream) -> Result<(), Error> {
Python::attach(|py| {
self.to_object(py)
.call_method1(py, "insert_record_stream", (stream.0,))?;
Ok(())
})
}
fn keys(&self) -> Result<Vec<Key>, Error> {
Python::attach(|py| {
let keys_iter = self.to_object(py).call_method0(py, "keys")?;
let mut keys = Vec::new();
for key_py in keys_iter
.cast_bound::<PyIterator>(py)
.map_err(|_| PyValueError::new_err("Expected iterator"))?
{
let key = key_py?.extract::<Key>()?;
keys.push(key);
}
Ok(keys)
})
}
fn make_mpdiffs(&self, keys: Vec<Key>) -> Result<Vec<MultiParentDiff>, Error> {
Python::attach(|py| {
let keys_py: Vec<_> = keys
.into_iter()
.map(|k| k.into_pyobject(py))
.collect::<Result<Vec<_>, _>>()?;
let keys_list = PyList::new(py, keys_py)?;
let result = self
.to_object(py)
.call_method1(py, "make_mpdiffs", (keys_list,))?;
let mut diffs = Vec::new();
for diff_py in result
.cast_bound::<PyIterator>(py)
.map_err(|_| PyValueError::new_err("Expected iterator"))?
{
let diff = diff_py?.extract::<MultiParentDiff>()?;
diffs.push(diff);
}
Ok(diffs)
})
}
fn get_parent_map(&self, keys: Vec<Key>) -> Result<HashMap<Key, Vec<Key>>, Error> {
Python::attach(|py| {
let keys_py: Vec<_> = keys
.into_iter()
.map(|k| k.into_pyobject(py))
.collect::<Result<Vec<_>, _>>()?;
let keys_list = PyList::new(py, keys_py)?;
let result_dict =
self.to_object(py)
.call_method1(py, "get_parent_map", (keys_list,))?;
let dict = result_dict
.cast_bound::<PyDict>(py)
.map_err(|_| PyValueError::new_err("Expected dict"))?;
let mut parent_map = HashMap::new();
for (key_py, parents_py) in dict {
let key = key_py.extract::<Key>()?;
let mut parents = Vec::new();
for parent_py in parents_py
.cast::<PyTuple>()
.map_err(|_| PyValueError::new_err("Expected tuple"))?
{
let parent = parent_py.extract::<Key>()?;
parents.push(parent);
}
parent_map.insert(key, parents);
}
Ok(parent_map)
})
}
fn get_known_graph_ancestry(&self, keys: Vec<Key>) -> Result<KnownGraph, Error> {
Python::attach(|py| {
let keys_py: Vec<_> = keys
.into_iter()
.map(|k| k.into_pyobject(py))
.collect::<Result<Vec<_>, _>>()?;
let keys_list = PyList::new(py, keys_py)?;
let graph_obj =
self.to_object(py)
.call_method1(py, "get_known_graph_ancestry", (keys_list,))?;
Ok(KnownGraph::new(graph_obj))
})
}
}
#[derive(Clone, Copy, Debug)]
pub enum RecordOrdering {
Unordered,
Topological,
GroupedByKey,
}
pub struct RecordStream(Py<PyAny>);
impl RecordStream {
pub fn iter(&self) -> Result<RecordStreamIterator, Error> {
Python::attach(|py| {
let iter = self.0.call_method0(py, "__iter__")?;
Ok(RecordStreamIterator(iter))
})
}
}
pub struct RecordStreamIterator(Py<PyAny>);
impl Iterator for RecordStreamIterator {
type Item = Result<Record, Error>;
fn next(&mut self) -> Option<Self::Item> {
Python::attach(|py| match self.0.call_method0(py, "__next__") {
Ok(record_py) => Some(record_py.bind(py).extract::<Record>().map_err(Into::into)),
Err(e) if e.is_instance_of::<pyo3::exceptions::PyStopIteration>(py) => None,
Err(e) => Some(Err(e.into())),
})
}
}
#[derive(Debug)]
pub struct Record {
pub key: Key,
pub storage_kind: String,
pub sha1: Option<String>,
pub parents: Vec<Key>,
}
impl<'a, 'py> FromPyObject<'a, 'py> for Record {
type Error = PyErr;
fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
let key = ob.getattr("key")?.extract::<Key>()?;
let storage_kind = ob.getattr("storage_kind")?.extract::<String>()?;
let sha1 = if let Ok(sha_bytes) = ob.getattr("sha1") {
if !sha_bytes.is_none() {
let bytes = sha_bytes
.cast::<PyBytes>()
.map_err(|_| PyValueError::new_err("Expected bytes"))?;
Some(
std::str::from_utf8(bytes.as_bytes())
.map_err(|_| PyValueError::new_err("Invalid UTF-8 in SHA1"))?
.to_string(),
)
} else {
None
}
} else {
None
};
let parents = ob.getattr("parents")?.extract::<Vec<Key>>()?;
Ok(Record {
key,
storage_kind,
sha1,
parents,
})
}
}
#[derive(Debug)]
pub struct MultiParentDiff {
pub key: Key,
pub parents: Vec<Key>,
pub hunks: Vec<DiffHunk>,
}
impl<'a, 'py> FromPyObject<'a, 'py> for MultiParentDiff {
type Error = PyErr;
fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
let tuple = ob
.cast::<PyTuple>()
.map_err(|_| PyValueError::new_err("Expected tuple"))?;
let key = tuple.get_item(0)?.extract::<Key>()?;
let parents = tuple.get_item(1)?.extract::<Vec<Key>>()?;
let hunks_py = tuple.get_item(2)?;
let mut hunks = Vec::new();
for hunk_py in hunks_py
.cast::<PyList>()
.map_err(|_| PyValueError::new_err("Expected list"))?
{
hunks.push(hunk_py.extract::<DiffHunk>()?);
}
Ok(MultiParentDiff {
key,
parents,
hunks,
})
}
}
#[derive(Debug)]
pub enum DiffHunk {
NewText(Vec<String>),
ParentText {
parent: usize,
start: usize,
end: usize,
},
}
impl<'a, 'py> FromPyObject<'a, 'py> for DiffHunk {
type Error = PyErr;
fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
let tuple = ob
.cast::<PyTuple>()
.map_err(|_| PyValueError::new_err("Expected tuple"))?;
let hunk_type = tuple.get_item(0)?.extract::<String>()?;
match hunk_type.as_str() {
"new" => {
let lines = tuple.get_item(1)?.extract::<Vec<String>>()?;
Ok(DiffHunk::NewText(lines))
}
"parent" => {
let parent = tuple.get_item(1)?.extract::<usize>()?;
let start = tuple.get_item(2)?.extract::<usize>()?;
let end = tuple.get_item(3)?.extract::<usize>()?;
Ok(DiffHunk::ParentText { parent, start, end })
}
_ => Err(PyValueError::new_err(format!(
"Unknown hunk type: {}",
hunk_type
))),
}
}
}
pub struct GenericVersionedFiles(Py<PyAny>);
impl GenericVersionedFiles {
pub fn new(py_obj: Py<PyAny>) -> Self {
Self(py_obj)
}
}
impl Clone for GenericVersionedFiles {
fn clone(&self) -> Self {
Python::attach(|py| GenericVersionedFiles(self.0.clone_ref(py)))
}
}
impl PyVersionedFiles for GenericVersionedFiles {
fn to_object(&self, py: Python) -> Py<PyAny> {
self.0.clone_ref(py)
}
}
impl<'py> IntoPyObject<'py> for GenericVersionedFiles {
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self.0.into_bound(py))
}
}
impl<'a, 'py> FromPyObject<'a, 'py> for GenericVersionedFiles {
type Error = PyErr;
fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
Ok(GenericVersionedFiles(ob.to_owned().unbind()))
}
}
#[cfg(test)]
mod tests;