use anyhow::Result;
use serde_json::{json, Value};
use crate::journal::{self, ChainStatus};
fn get_str<'a>(v: &'a Value, k: &str) -> &'a str {
v.get(k).and_then(|x| x.as_str()).unwrap_or("")
}
fn get_u64(v: &Value, k: &str) -> u64 {
v.get(k).and_then(|x| x.as_u64()).unwrap_or(0)
}
pub fn run(file: &str, actor: Option<&str>) -> Result<Value> {
let _lock = journal::lock(file)?;
let entries = journal::read_entries(file)?;
if entries.is_empty() {
return Ok(json!({ "command": "undo", "error": "nothing_to_undo" }));
}
let current = crate::hash::sha256_file(file)?;
let timestamp = journal::iso_timestamp(None);
let resolved_actor = journal::resolve_actor(actor);
match journal::chain_status(file, ¤t)? {
ChainStatus::Ok => {}
ChainStatus::Genesis => {
return Ok(json!({ "command": "undo", "error": "nothing_to_undo" }));
}
ChainStatus::ExternalEdit => {
let marker =
journal::append_adoption_marker(file, ¤t, ×tamp, &resolved_actor)?;
return Ok(json!({
"command": "undo",
"error": "external_edit_detected",
"actual": current,
"adopted_rev": marker.rev,
}));
}
}
let head = entries.last().expect("non-empty");
let head_rev = get_u64(head, "rev");
let target_hash = get_str(head, "base_hash").to_string();
let target = match entries
.iter()
.rev()
.find(|e| get_str(e, "result_hash") == target_hash)
{
Some(t) => t,
None => return Ok(json!({ "command": "undo", "error": "no_prior_snapshot" })),
};
let target_rev = get_u64(target, "rev");
let snapshot = journal::rev_path(file, target_rev);
let bytes = match std::fs::read(&snapshot) {
Ok(b) => b,
Err(_) => {
return Ok(json!({
"command": "undo",
"error": "backup_missing",
"restored_rev": target_rev,
}))
}
};
if crate::hash::sha256_bytes(&bytes) != target_hash {
return Ok(json!({
"command": "undo",
"error": "backup_corrupt",
"restored_rev": target_rev,
}));
}
let new_rev = journal::next_rev(file)?;
let ops = json!({ "undo_of_rev": head_rev, "restored_rev": target_rev });
let receipt = journal::commit(
file,
&bytes,
new_rev,
"undo",
¤t,
&target_hash,
ops,
×tamp,
&resolved_actor,
None,
None,
)?;
Ok(json!({
"command": "undo",
"rev": receipt.rev,
"base_hash": current,
"result_hash": target_hash,
"undone_rev": head_rev,
"restored_rev": target_rev,
}))
}