papayu/src-tauri/src/commands/undo_last_tx.rs
Yuriy e76236dc55 Initial commit: papa-yu v2.4.4
- Schema version (x_schema_version, schema_hash) в prompt/trace
- Кеш read/search/logs/env (ContextCache) в plan-цикле
- Контекст-диета: MAX_FILES=8, MAX_FILE_CHARS=20k, MAX_TOTAL_CHARS=120k
- Plan→Apply двухфазность, NO_CHANGES, path sanitization
- Protected paths, content validation, EOL normalization
- Trace (PAPAYU_TRACE), redaction (PAPAYU_TRACE_RAW)
- Preview diff, undo/redo, transactional apply

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-01-31 11:33:19 +03:00

98 lines
2.8 KiB
Rust

//! v3.1: откат последней транзакции из history/tx + history/snapshots
use std::fs;
use std::path::{Path, PathBuf};
use tauri::{AppHandle, Manager};
fn copy_dir(src: &Path, dst: &Path) -> Result<(), String> {
fs::create_dir_all(dst).map_err(|e| e.to_string())?;
for e in fs::read_dir(src).map_err(|e| e.to_string())? {
let e = e.map_err(|e| e.to_string())?;
let sp = e.path();
let dp = dst.join(e.file_name());
let ft = e.file_type().map_err(|e| e.to_string())?;
if ft.is_dir() {
copy_dir(&sp, &dp)?;
} else if ft.is_file() {
fs::copy(&sp, &dp).map_err(|e| e.to_string())?;
}
}
Ok(())
}
#[tauri::command]
pub async fn undo_last_tx(app: AppHandle, path: String) -> Result<bool, String> {
let data_dir = app.path().app_data_dir().map_err(|e| e.to_string())?;
let tx_dir = data_dir.join("history").join("tx");
let snap_base = data_dir.join("history").join("snapshots");
if !tx_dir.exists() {
return Ok(false);
}
let mut items: Vec<(std::time::SystemTime, PathBuf)> = vec![];
for e in fs::read_dir(&tx_dir).map_err(|e| e.to_string())? {
let e = e.map_err(|e| e.to_string())?;
let meta = e.metadata().map_err(|e| e.to_string())?;
let m = meta.modified().map_err(|e| e.to_string())?;
items.push((m, e.path()));
}
items.sort_by(|a, b| b.0.cmp(&a.0));
let last = match items.first() {
Some((_, p)) => p.clone(),
None => return Ok(false),
};
let raw = fs::read_to_string(&last).map_err(|e| e.to_string())?;
let v: serde_json::Value = serde_json::from_str(&raw).map_err(|e| e.to_string())?;
let tx_id = v
.get("txId")
.and_then(|x| x.as_str())
.ok_or("txId missing")?;
let tx_path = v.get("path").and_then(|x| x.as_str()).unwrap_or("");
if tx_path != path {
return Ok(false);
}
let snap_dir = snap_base.join(tx_id);
if !snap_dir.exists() {
return Ok(false);
}
let root = PathBuf::from(&path);
if !root.exists() {
return Ok(false);
}
let exclude = [
".git",
"node_modules",
"dist",
"build",
".next",
"target",
".cache",
"coverage",
];
for entry in fs::read_dir(&root).map_err(|e| e.to_string())? {
let entry = entry.map_err(|e| e.to_string())?;
let p = entry.path();
let name = entry.file_name();
if exclude
.iter()
.any(|x| name.to_string_lossy().as_ref() == *x)
{
continue;
}
if p.is_dir() {
fs::remove_dir_all(&p).map_err(|e| e.to_string())?;
} else {
fs::remove_file(&p).map_err(|e| e.to_string())?;
}
}
copy_dir(&snap_dir, &root)?;
Ok(true)
}