weavatrix-worktree 0.2.0

Bounded, crash-recoverable multi-file worktree edits for source refactoring
Documentation
use std::io;

use crate::{error::WorktreeError, filesystem::SlotEvidence, options::WorktreeOptions};

use super::evidence::{
    corrupt, current_state, foreign_state, matches_present_evidence, max_bytes, path_io,
    path_recovery_io, path_recovery_required,
};
use crate::operation::recovery_model::{ParsedJournal, RecoveryPath, StateSpec};

pub(super) fn linked_create_is_exact(
    path: &RecoveryPath,
    options: WorktreeOptions,
    parsed: &ParsedJournal,
) -> Result<bool, WorktreeError> {
    if !matches!(path.before, StateSpec::Absent) {
        return Ok(false);
    }
    let Some(stage) = path.stage_name.as_deref() else {
        return Ok(false);
    };
    match path.access.same_file_as_artifact(stage) {
        Ok(true) => {
            let evidence = path
                .access
                .linked_artifact_evidence(stage, max_bytes(options))
                .map_err(|error| {
                    path_io(
                        parsed,
                        path,
                        "failed to verify linked operation install",
                        error,
                    )
                })?;
            let Some(spec) = path.after.present() else {
                return Err(corrupt(
                    parsed,
                    path,
                    "linked install has no present after-state",
                ));
            };
            if !matches_present_evidence(spec, evidence, path.stage_identity) {
                return Err(foreign_state(
                    parsed,
                    path,
                    "linked operation install does not match exact staged evidence",
                ));
            }
            Ok(true)
        }
        Ok(false) => Ok(false),
        Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(false),
        Err(error) => Err(path_recovery_io(
            path,
            "failed to inspect linked operation install",
            error,
        )),
    }
}

pub(super) fn linked_backup_restore_is_exact(
    path: &RecoveryPath,
    options: WorktreeOptions,
    parsed: &ParsedJournal,
) -> Result<bool, WorktreeError> {
    let (StateSpec::Present(spec), StateSpec::Absent) = (&path.before, &path.after) else {
        return Ok(false);
    };
    let Some(backup) = path.backup_name.as_deref() else {
        return Ok(false);
    };
    match path.access.same_file_as_artifact(backup) {
        Ok(true) => {
            let evidence = path
                .access
                .linked_artifact_evidence(backup, max_bytes(options))
                .map_err(|error| {
                    path_io(
                        parsed,
                        path,
                        "failed to verify linked operation backup restore",
                        error,
                    )
                })?;
            if !matches_present_evidence(spec, evidence, path.backup_identity) {
                return Err(foreign_state(
                    parsed,
                    path,
                    "linked operation backup restore does not match exact before evidence",
                ));
            }
            Ok(true)
        }
        Ok(false) => Ok(false),
        Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(false),
        Err(error) => Err(path_recovery_io(
            path,
            "failed to inspect linked operation backup restore",
            error,
        )),
    }
}

pub(super) fn rollback_linked_create(
    path: &RecoveryPath,
    options: WorktreeOptions,
) -> Result<(), WorktreeError> {
    let stage = path.stage_name.as_deref().ok_or_else(|| {
        path_recovery_required(path, "linked operation install has no staged artifact name")
    })?;
    path.access
        .rollback_linked_install(stage)
        .map_err(|error| {
            path_recovery_io(path, "failed to reverse linked operation install", error)
        })?;
    if current_state(path, options)? != SlotEvidence::Absent {
        return Err(path_recovery_required(
            path,
            "linked operation install rollback did not restore absence",
        ));
    }
    Ok(())
}

pub(super) fn finish_linked_backup_restore(
    path: &RecoveryPath,
    options: WorktreeOptions,
) -> Result<(), WorktreeError> {
    let backup = path.backup_name.as_deref().ok_or_else(|| {
        path_recovery_required(path, "linked operation backup restore has no artifact name")
    })?;
    let identity = path.backup_identity.ok_or_else(|| {
        path_recovery_required(
            path,
            "linked operation backup restore has no durable identity",
        )
    })?;
    path.access
        .finish_linked_install(backup, identity)
        .map_err(|error| {
            path_recovery_io(
                path,
                "failed to finish linked operation backup restore",
                error,
            )
        })?;
    let StateSpec::Present(before) = &path.before else {
        return Err(path_recovery_required(
            path,
            "linked operation backup restore has no present before-state",
        ));
    };
    if !super::evidence::matches_present(before, current_state(path, options)?, Some(identity)) {
        return Err(path_recovery_required(
            path,
            "linked operation backup restore failed exact verification",
        ));
    }
    Ok(())
}