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}