use std::collections::BTreeMap;
use prikk_error::{PrikkError, Result};
use prikk_object::ObjectId;
use crate::layout::RepositoryLayout;
use crate::patch_inverse::prepare_patch_inverse_plan;
use crate::patch_replay::{ReplayManifest, replay_supported_patch_chain};
use crate::snapshot::SnapshotManifest;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RollbackPreviewPlan {
pub ref_name: String,
pub target_block_id: ObjectId,
pub block_count: usize,
pub patch_count: usize,
pub inverse_operation_count: usize,
pub inverse_patch_id_hint: ObjectId,
pub current_file_count: usize,
pub current_content_bytes: u64,
pub preview_file_count: usize,
pub preview_content_bytes: u64,
pub change_count: usize,
pub would_create_files: usize,
pub would_delete_files: usize,
pub would_replace_files: usize,
pub changes: Vec<RollbackPreviewChange>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RollbackPreviewChange {
pub path: String,
pub kind: RollbackPreviewChangeKind,
pub current_bytes: Option<u64>,
pub preview_bytes: Option<u64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RollbackPreviewChangeKind {
WouldCreate,
WouldDelete,
WouldReplace,
}
impl RollbackPreviewChangeKind {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::WouldCreate => "would-create",
Self::WouldDelete => "would-delete",
Self::WouldReplace => "would-replace",
}
}
}
pub fn prepare_rollback_preview(
layout: &RepositoryLayout,
ref_name: &str,
) -> Result<RollbackPreviewPlan> {
let inverse = prepare_patch_inverse_plan(layout, ref_name)?;
let replay = replay_supported_patch_chain(layout, ref_name)?;
if inverse.target_block_id != replay.target_block_id {
return Err(PrikkError::Integrity(format!(
"inverse target {} does not match replay target {}",
inverse.target_block_id, replay.target_block_id
)));
}
let current = replay_manifest_to_map(&replay.manifest);
let preview = manifest_to_map(&replay.baseline_manifest);
let changes = compare_maps(¤t, &preview);
let would_create_files = changes
.iter()
.filter(|change| change.kind == RollbackPreviewChangeKind::WouldCreate)
.count();
let would_delete_files = changes
.iter()
.filter(|change| change.kind == RollbackPreviewChangeKind::WouldDelete)
.count();
let would_replace_files = changes
.iter()
.filter(|change| change.kind == RollbackPreviewChangeKind::WouldReplace)
.count();
Ok(RollbackPreviewPlan {
ref_name: ref_name.to_string(),
target_block_id: replay.target_block_id,
block_count: inverse.block_count,
patch_count: inverse.patch_count,
inverse_operation_count: inverse.inverse_operation_count,
inverse_patch_id_hint: inverse.inverse_patch_id_hint,
current_file_count: replay.manifest.files.len(),
current_content_bytes: replay.manifest.total_content_bytes(),
preview_file_count: replay.baseline_manifest.files.len(),
preview_content_bytes: replay.baseline_manifest.total_content_bytes(),
change_count: changes.len(),
would_create_files,
would_delete_files,
would_replace_files,
changes,
})
}
fn manifest_to_map(manifest: &SnapshotManifest) -> BTreeMap<String, Vec<u8>> {
let mut files = BTreeMap::new();
for entry in &manifest.files {
files.insert(entry.path.as_str().to_string(), entry.bytes.clone());
}
files
}
fn replay_manifest_to_map(manifest: &ReplayManifest) -> BTreeMap<String, Vec<u8>> {
let mut files = BTreeMap::new();
for entry in &manifest.files {
files.insert(entry.path.as_str().to_string(), entry.bytes.clone());
}
files
}
fn compare_maps(
current: &BTreeMap<String, Vec<u8>>,
preview: &BTreeMap<String, Vec<u8>>,
) -> Vec<RollbackPreviewChange> {
let mut changes = Vec::new();
for (path, current_bytes) in current {
match preview.get(path) {
Some(preview_bytes) if preview_bytes == current_bytes => {}
Some(preview_bytes) => changes.push(RollbackPreviewChange {
path: path.clone(),
kind: RollbackPreviewChangeKind::WouldReplace,
current_bytes: Some(current_bytes.len() as u64),
preview_bytes: Some(preview_bytes.len() as u64),
}),
None => changes.push(RollbackPreviewChange {
path: path.clone(),
kind: RollbackPreviewChangeKind::WouldDelete,
current_bytes: Some(current_bytes.len() as u64),
preview_bytes: None,
}),
}
}
for (path, preview_bytes) in preview {
if current.contains_key(path) {
continue;
}
changes.push(RollbackPreviewChange {
path: path.clone(),
kind: RollbackPreviewChangeKind::WouldCreate,
current_bytes: None,
preview_bytes: Some(preview_bytes.len() as u64),
});
}
changes.sort_by(|left, right| left.path.cmp(&right.path));
changes
}
#[cfg(test)]
mod tests;