use std::{
fs::{remove_dir, remove_file},
path::PathBuf,
};
use anyhow::Result;
use path_absolutize::Absolutize;
use reflink::reflink_or_copy;
use thiserror::Error;
use walkdir::WalkDir;
use crate::{
containing_table_info, is_hidden,
paths::{table_snapshot_path, PathBufExt},
XATTR_HEAD,
};
#[derive(Copy, Clone)]
pub enum NewFileHandling {
Keep,
Delete,
}
#[derive(Error, Debug)]
pub enum RestoreErr {
#[error("No prior commit known for table {0:?}")]
NoPriorCommit(PathBuf),
#[error("{0:?} is not contained by a table")]
PathNotContainedByTable(PathBuf),
}
pub fn reset_path(
path: &PathBuf,
commit: &str,
recursive: bool,
verbose: bool,
new_file_handling: NewFileHandling,
) -> Result<()> {
let max_depth = if recursive { usize::MAX } else { 0 };
let info =
containing_table_info(path)?.ok_or(RestoreErr::PathNotContainedByTable(path.clone()))?;
let table_path = info.path_abs();
let snapshot_path = table_snapshot_path(table_path, commit);
for entry in WalkDir::new(path)
.max_depth(max_depth)
.into_iter()
.filter_entry(|e| !is_hidden(e))
{
let entry = entry?;
let path = entry.path().to_path_buf();
let abs_path = path.absolutize().unwrap().to_path_buf();
let rel_path = abs_path.relativize(table_path)?;
let snapshot_file_path = {
let mut p = snapshot_path.clone();
p.push(&rel_path);
p
}
.absolutize()
.unwrap()
.to_path_buf();
if abs_path.exists() {
if snapshot_file_path.exists() {
if snapshot_file_path.is_dir() {
if abs_path.is_dir() {
} else {
remove_file(&abs_path)?;
let working_dest_dir = &abs_path;
for entry in WalkDir::new(&snapshot_file_path).into_iter() {
let entry = entry?;
let snapshot_rel = entry
.path()
.to_path_buf()
.relativize(&snapshot_file_path)
.unwrap();
let working = snapshot_rel.resolve_curdir(working_dest_dir).unwrap();
reflink_or_copy(entry.path(), &working)?;
}
if verbose {
println!("r{}", path.display());
}
}
} else {
if abs_path.is_dir() {
remove_dir(&abs_path)?;
}
reflink_or_copy(&snapshot_file_path, abs_path)?;
if verbose {
println!("r{}", path.display());
}
}
} else {
match new_file_handling {
NewFileHandling::Delete => {
remove_file(abs_path)?;
if verbose {
println!("-{}", path.display());
}
}
NewFileHandling::Keep => { }
}
}
} else {
if snapshot_file_path.exists() {
reflink_or_copy(&snapshot_file_path, abs_path)?;
if verbose {
println!("r{}", path.display());
}
} else {
eprintln!(
"WTF? {} exists neither in working directory nor in most recent commit",
rel_path.display()
);
}
}
}
Ok(())
}
pub fn restore(
paths: &Vec<PathBuf>,
recursive: bool,
verbose: bool,
new_file_handling: NewFileHandling,
) -> Result<()> {
for path in paths {
let info = containing_table_info(path)?
.ok_or(RestoreErr::PathNotContainedByTable(path.clone()))?;
let table_path = info.path_abs();
let uuid_bytes = xattr::get(info.path_abs(), XATTR_HEAD.to_osstring())?
.ok_or(RestoreErr::NoPriorCommit(table_path.clone()))?;
let uuid = String::from_utf8(uuid_bytes)?;
reset_path(path, uuid.as_str(), recursive, verbose, new_file_handling)?;
}
Ok(())
}