use crate::node::{NESTED_SEP, NamedRef};
use gantz_ca::{CaHash, CommitAddr, Registry};
use gantz_core::node::graph::Graph;
use std::collections::HashMap;
use std::time::Duration;
pub trait AsNamedRefMut {
fn as_named_ref_mut(&mut self) -> Option<&mut NamedRef>;
}
pub trait AsNamedRef {
fn as_named_ref(&self) -> Option<&NamedRef>;
}
#[derive(Clone, Debug)]
pub struct Moved {
pub name: String,
pub old_commit: CommitAddr,
pub new_commit: CommitAddr,
}
fn depth(name: &str) -> usize {
name.matches(NESTED_SEP).count()
}
fn rewrite_refs<N>(
graph: &Graph<N>,
mut mutate: impl FnMut(&mut NamedRef) -> bool,
) -> (Graph<N>, bool)
where
N: Clone + AsNamedRefMut,
{
let mut g = graph.clone();
let mut changed = false;
for weight in g.node_weights_mut() {
if let Some(named_ref) = weight.as_named_ref_mut() {
changed |= mutate(named_ref);
}
}
(g, changed)
}
fn commit_rewritten<N>(
registry: &mut Registry<Graph<N>>,
timestamp: Duration,
name: &str,
source_commit: CommitAddr,
mutate: impl FnMut(&mut NamedRef) -> bool,
) -> Option<Moved>
where
N: Clone + CaHash + AsNamedRefMut,
{
let (g, changed) = rewrite_refs(registry.commit_graph_ref(&source_commit)?, mutate);
if !changed {
return None;
}
let graph_ca = gantz_ca::graph_addr(&g);
let new_commit = registry.commit_graph_to_name(timestamp, graph_ca, || g, name);
Some(Moved {
name: name.to_string(),
old_commit: source_commit,
new_commit,
})
}
fn remap_ref(named_ref: &mut NamedRef, remap: &HashMap<String, (String, CommitAddr)>) -> bool {
match remap.get(named_ref.name()) {
Some((new_name, new_commit)) => {
named_ref.rename(new_name.clone(), (*new_commit).into());
true
}
None => false,
}
}
pub fn fork_nested<N>(
registry: &mut Registry<Graph<N>>,
timestamp: Duration,
old: &str,
new: &str,
) -> Vec<Moved>
where
N: Clone + CaHash + AsNamedRefMut,
{
let old_prefix = format!("{old}{NESTED_SEP}");
let mut descendants: Vec<String> = registry
.names()
.keys()
.filter(|n| n.starts_with(&old_prefix))
.cloned()
.collect();
descendants.sort_by(|a, b| depth(b).cmp(&depth(a)).then_with(|| a.cmp(b)));
let mut remap: HashMap<String, (String, CommitAddr)> = HashMap::new();
let mut moves = Vec::new();
for d in &descendants {
let d_new = format!("{new}{}", &d[old.len()..]);
let Some(&commit) = registry.names().get(d) else {
continue;
};
let Some(graph) = registry.commit_graph_ref(&commit) else {
continue;
};
let (g, _) = rewrite_refs(graph, |nr| remap_ref(nr, &remap));
let graph_ca = gantz_ca::graph_addr(&g);
let new_commit = registry.commit_graph_to_name(timestamp, graph_ca, || g, &d_new);
remap.insert(d.clone(), (d_new.clone(), new_commit));
moves.push(Moved {
name: d_new,
old_commit: commit,
new_commit,
});
}
if let Some(&root_commit) = registry.names().get(new) {
moves.extend(commit_rewritten(
registry,
timestamp,
new,
root_commit,
|nr| remap_ref(nr, &remap),
));
}
moves
}
pub fn resync<N>(registry: &mut Registry<Graph<N>>, timestamp: Duration) -> Vec<Moved>
where
N: Clone + CaHash + AsNamedRefMut,
{
let mut order: Vec<String> = registry.names().keys().cloned().collect();
order.sort_by(|a, b| depth(b).cmp(&depth(a)).then_with(|| a.cmp(b)));
let mut current: HashMap<String, CommitAddr> = registry
.names()
.iter()
.map(|(n, ca)| (n.clone(), *ca))
.collect();
let mut moves = Vec::new();
let max_passes = order.len() + 1;
for _ in 0..max_passes {
let mut changed_any = false;
for name in &order {
let Some(&commit_ca) = current.get(name) else {
continue;
};
let resolve = |m: &str| current.get(m).copied().map(gantz_ca::ContentAddr::from);
if let Some(moved) = commit_rewritten(registry, timestamp, name, commit_ca, |nr| {
nr.resync(&resolve)
}) {
current.insert(name.clone(), moved.new_commit);
moves.push(moved);
changed_any = true;
}
}
if !changed_any {
break;
}
}
moves
}
pub fn promote_nested<N>(
registry: &mut Registry<Graph<N>>,
timestamp: Duration,
old_nested: &str,
new_name: &str,
) -> Vec<Moved>
where
N: Clone + CaHash + AsNamedRefMut,
{
let Some((parent, _)) = old_nested.rsplit_once(NESTED_SEP) else {
return Vec::new();
};
let parent = parent.to_string();
let (Some(&new_commit), Some(&parent_commit)) = (
registry.names().get(new_name),
registry.names().get(&parent),
) else {
return Vec::new();
};
let mut moves = Vec::new();
moves.extend(commit_rewritten(
registry,
timestamp,
&parent,
parent_commit,
|nr| {
if nr.name() == old_nested {
nr.rename(new_name.to_string(), new_commit.into());
true
} else {
false
}
},
));
let child_prefix = format!("{old_nested}{NESTED_SEP}");
let orphans: Vec<String> = registry
.names()
.keys()
.filter(|n| n.as_str() == old_nested || n.starts_with(&child_prefix))
.cloned()
.collect();
for orphan in orphans {
registry.remove_name(&orphan);
}
moves
}