use pyo3::prelude::*;
use pyo3::types::{PyList, PyTuple};
use std::collections::HashMap;
use crate::graph::GraphNode;
#[derive(Debug, Clone)]
pub struct MergeSortEntry<T: GraphNode> {
pub sequence: usize,
pub node: T,
pub merge_depth: usize,
pub revno: Vec<u32>,
pub end_of_merge: bool,
}
impl<T: GraphNode> MergeSortEntry<T> {
pub fn revno_str(&self) -> String {
let mut out = String::new();
for (i, n) in self.revno.iter().enumerate() {
if i > 0 {
out.push('.');
}
out.push_str(&n.to_string());
}
out
}
}
pub fn merge_sort<T: GraphNode>(
graph: &HashMap<T, Vec<T>>,
branch_tip: &T,
) -> Result<Vec<MergeSortEntry<T>>, crate::error::Error> {
Python::attach(|py| {
let tsort = py
.import("breezy.tsort")
.or_else(|_| py.import("vcsgraph.tsort"))?;
let py_graph = pyo3::types::PyDict::new(py);
for (node, parents) in graph {
let py_parents = PyList::empty(py);
for parent in parents {
py_parents.append(parent.to_pyobject(py)?)?;
}
py_graph.set_item(node.to_pyobject(py)?, py_parents)?;
}
let kwargs = pyo3::types::PyDict::new(py);
kwargs.set_item("generate_revno", true)?;
let result = tsort.call_method(
"merge_sort",
(py_graph, branch_tip.to_pyobject(py)?),
Some(&kwargs),
)?;
let mut out = Vec::new();
for item in result.try_iter()? {
let item = item?;
let tup = item.cast::<PyTuple>().map_err(PyErr::from)?;
if tup.len() != 5 {
return Err(crate::error::Error::from(
pyo3::exceptions::PyValueError::new_err(
"Expected 5-tuple from merge_sort(generate_revno=True)",
),
));
}
let sequence: usize = tup.get_item(0)?.extract()?;
let node = T::from_pyobject(&tup.get_item(1)?)?;
let merge_depth: usize = tup.get_item(2)?.extract()?;
let revno: Vec<u32> = tup.get_item(3)?.extract()?;
let end_of_merge: bool = tup.get_item(4)?.extract()?;
out.push(MergeSortEntry {
sequence,
node,
merge_depth,
revno,
end_of_merge,
});
}
Ok(out)
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::revisionid::RevisionId;
#[test]
fn test_merge_sort_empty_like() {
crate::init();
let tip = RevisionId::from(b"tip".to_vec());
let mut graph = HashMap::new();
graph.insert(tip.clone(), vec![]);
let sorted = merge_sort(&graph, &tip).unwrap();
assert_eq!(sorted.len(), 1);
assert_eq!(sorted[0].node, tip);
assert_eq!(sorted[0].merge_depth, 0);
assert_eq!(sorted[0].revno, vec![1]);
assert!(sorted[0].end_of_merge);
assert_eq!(sorted[0].revno_str(), "1");
}
#[test]
fn test_merge_sort_linear() {
crate::init();
let r1 = RevisionId::from(b"a".to_vec());
let r2 = RevisionId::from(b"b".to_vec());
let r3 = RevisionId::from(b"c".to_vec());
let mut graph = HashMap::new();
graph.insert(r1.clone(), vec![]);
graph.insert(r2.clone(), vec![r1.clone()]);
graph.insert(r3.clone(), vec![r2.clone()]);
let sorted = merge_sort(&graph, &r3).unwrap();
let nodes: Vec<_> = sorted.iter().map(|e| e.node.clone()).collect();
assert_eq!(nodes, vec![r3, r2, r1]);
let revnos: Vec<_> = sorted.iter().map(|e| e.revno_str()).collect();
assert_eq!(revnos, vec!["3", "2", "1"]);
}
}