Skip to main content

cosh_tools/fs/
rollback.rs

1//! Rollback tool — restores a file to a previously recorded session version.
2//!
3//! Wraps [`cosh_sdk::rollback::restore`] with write-permission enforcement
4//! via [`FsMetadata`].
5
6use cosh_sdk::rollback::{RestoreInput, restore};
7
8use super::types::{FsMetadata, FsRollback};
9
10use serde::Serialize;
11
12/// Result of a successful rollback.
13#[derive(Debug, Serialize)]
14pub struct RollbackResult {
15    /// Path of the restored file.
16    pub path: String,
17    /// Content hash of the version now on disk. Use this as the anchor for
18    /// any follow-up read or edit operations.
19    pub file_hash: String,
20    /// Hashline header `¶path#HASH` for the restored version. Include this
21    /// in the session context so subsequent tools have a valid anchor.
22    pub header: String,
23    /// Content hash of what was on disk before the restore. Pass this back
24    /// to rollback to undo the restore.
25    pub replaced_hash: String,
26    /// Non-fatal warning when an anomaly was detected but the restore still
27    /// succeeded (e.g., the file was modified externally since the snapshot).
28    pub warning: Option<String>,
29    /// Passive LSP feedback collected after the restore (errors by default,
30    /// warnings when the caller opted in). `None` when LSP is disabled or
31    /// nothing was found.
32    #[serde(default, skip_serializing_if = "Option::is_none")]
33    pub lsp_notes: Option<super::types::LspNotes>,
34}
35
36/// Restore a file to a previously recorded version.
37///
38/// Checks write permissions before delegating to the rollback engine. If
39/// `hash` is empty, the engine restores the version immediately
40/// preceding the current file content.
41///
42/// # Errors
43///
44/// Returns an error string when:
45/// - Write permission is denied for the path.
46/// - The path has no rollback history in the current session.
47/// - The requested hash is not found in the session history.
48/// - The file is already at the requested version.
49/// - There is no preceding version to restore to.
50/// - The file was modified externally and `hash` is empty.
51/// - A disk write error occurs.
52pub async fn rollback(
53    config: &FsRollback,
54    metadata: FsMetadata,
55    path: &str,
56    hash: &str,
57) -> Result<RollbackResult, String> {
58    let _ = config;
59    let validated_path = metadata
60        .fs_guard(path)
61        .map_err(|e| format!("restore permission denied for `{path}`; {e}",))?;
62
63    let path_str = validated_path.to_string_lossy().to_string();
64    let hash = hash.trim();
65    let hash = if hash.is_empty() {
66        None
67    } else {
68        Some(hash.to_owned())
69    };
70
71    let out = restore(RestoreInput {
72        path: path_str,
73        hash,
74    })
75    .await?;
76
77    Ok(RollbackResult {
78        path: out.path,
79        file_hash: out.file_hash,
80        header: out.header,
81        replaced_hash: out.replaced_hash,
82        warning: out.warning,
83        lsp_notes: None,
84    })
85}