use std::path::Path;
use crate::engine::{Engine, EngineError};
use crate::vcs::{Actor, CommitContext};
#[derive(Debug, Clone)]
pub struct MemSweepOutcome {
pub rewritten_mems: Vec<String>,
pub logical_operation_id: String,
}
impl Engine {
pub fn rewrite_mem_references(
&mut self,
old_mem: &str,
new_mem: &str,
note: Option<&str>,
) -> Result<MemSweepOutcome, EngineError> {
let logical_op_id = crate::provenance::mint_logical_operation_id();
let mut mem_order: Vec<usize> = Vec::new();
if let Some(own_idx) = self.mounts.iter().position(|m| {
m.mount.mem == old_mem && m.mount.capability == crate::workspace::MountCapability::Write
}) {
mem_order.push(own_idx);
}
let mut peer_idxs: Vec<usize> = self
.mounts
.iter()
.enumerate()
.filter(|(_, m)| {
m.mount.mem != old_mem
&& m.mount.capability == crate::workspace::MountCapability::Write
})
.map(|(i, _)| i)
.collect();
peer_idxs.sort_by(|a, b| self.mounts[*a].mount.mem.cmp(&self.mounts[*b].mount.mem));
mem_order.extend(peer_idxs);
let mut rewritten_mems: Vec<String> = Vec::new();
for mount_idx in mem_order {
let mem_name = self.mounts[mount_idx].mount.mem.clone();
let backend = self.mounts[mount_idx].backend.as_ref();
let head_snapshot = backend.current_head()?;
let mut changed: Vec<(std::path::PathBuf, String)> = Vec::new();
for rel_path in backend.list_entities()? {
let Some(bytes) = backend.read_entity(&rel_path)? else {
continue;
};
let Ok(text) = String::from_utf8(bytes) else {
continue;
};
let (rewritten, count) =
crate::entity::wikilink_rewrite::rewrite_mem_prefix(&text, old_mem, new_mem);
if count > 0 {
changed.push((rel_path, rewritten));
}
}
let mut anchors_changed = false;
let mut sidecar_bytes: Option<Vec<u8>> = None;
if mem_name == old_mem {
let sidecar_raw = backend.read_anchors_sidecar()?;
if let Some(raw) = sidecar_raw {
let sidecar = crate::anchor::AnchorSidecar::from_bytes(&raw)
.map_err(|e| EngineError::Mem(format!("anchors sidecar parse: {e}")))?;
let prefix = format!("{old_mem}--");
let mut next = crate::anchor::AnchorSidecar::default();
for (entity_id, anchors) in &sidecar.entities {
let new_key = match entity_id.strip_prefix(&prefix) {
Some(rest) => {
anchors_changed = true;
format!("{new_mem}--{rest}")
}
None => entity_id.clone(),
};
next.set(&new_key, anchors.clone());
}
if anchors_changed {
sidecar_bytes = Some(next.to_bytes());
}
}
}
if changed.is_empty() && !anchors_changed {
continue;
}
for (rel_path, text) in &changed {
backend.write_entity(Path::new(rel_path), text.as_bytes())?;
}
if let Some(bytes) = sidecar_bytes {
backend.write_anchors_sidecar(&bytes)?;
}
let subject = format!(
"memstead: rename mem `{old_mem}` → `{new_mem}` (reference rewrite in `{mem_name}`)"
);
let ctx = CommitContext {
actor: Actor::Agent,
client: None,
tool: Some("mem rename"),
note: note.map(String::from),
role: self.current_role,
logical_operation_id: Some(logical_op_id.as_str()),
entity_ids: None,
};
let commit_result =
backend.commit_with_expected_parent(&subject, &ctx, head_snapshot.as_deref());
let commit_sha = match commit_result {
Ok(sha) => sha,
Err(crate::backend::BackendError::ParentMismatch { .. }) => {
return Err(EngineError::RenamePartialFailure {
committed_mems: rewritten_mems,
failed_mem: mem_name,
failure_cause: "drift".to_string(),
});
}
Err(e) => return Err(e.into()),
};
self.record_self_write(mount_idx, &commit_sha);
self.stamp_mutation_versions(mount_idx);
rewritten_mems.push(mem_name);
}
Ok(MemSweepOutcome {
rewritten_mems,
logical_operation_id: logical_op_id,
})
}
}