use crate::conflict_side_source::{copy_located, locate};
use crate::iso_time::format_iso_milliseconds;
use crate::private_storage::{create_private_directory, create_private_directory_all, sync_directory};
use std::collections::BTreeSet;
use std::ffi::OsString;
use std::os::unix::ffi::OsStringExt;
use std::path::{Path, PathBuf};
pub use crate::conflict_side_decision::{SideDecision, decide_sides, literal_path};
pub use crate::conflict_side_notice::{clean_paths_note, saved_notice};
pub use crate::conflict_side_source::{FailureKind, SaveFailure};
pub use crate::conflict_side_state::{Operation, conflicted_paths, operation_in_progress};
pub const SAVED_DIRECTORY: &str = "cli-git/discarded";
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SavedCopy {
pub path: String,
pub saved_to: Option<PathBuf>,
}
fn stamp(milliseconds: i64) -> String {
let mut name: String = String::new();
for each in format_iso_milliseconds(milliseconds).chars().take(19) {
name.push(if each == ':' { '-' } else { each });
}
return name;
}
fn no_pause() {}
fn depth_deepest_first(path: &&PathBuf) -> std::cmp::Reverse<usize> {
return std::cmp::Reverse(path.components().count());
}
fn fresh_directory(saved_root: &Path, name: &str) -> std::io::Result<PathBuf> {
create_private_directory_all(saved_root)?;
for number in 0_u32..1000 {
let candidate: PathBuf = if number == 0 { saved_root.join(name) } else { saved_root.join(format!("{name}-{number}")) };
match create_private_directory(candidate.as_path()) {
Ok(()) => return Ok(candidate),
Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => {}
Err(error) => return Err(error),
}
}
return Err(std::io::Error::other("a thousand copies were saved in this second"));
}
pub fn save_copies(root: &Path, common_dir: &Path, paths: &[Vec<u8>], milliseconds: i64) -> Result<Vec<SavedCopy>, SaveFailure> {
let saved_root: PathBuf = common_dir.join(SAVED_DIRECTORY);
let mut copies: Vec<SavedCopy> = Vec::new();
let mut directory: Option<PathBuf> = None;
let mut written_directories: BTreeSet<PathBuf> = BTreeSet::new();
for path in paths {
let shown: String = String::from_utf8_lossy(path).into_owned();
let Some(located) = locate(root, path.as_slice())? else {
copies.push(SavedCopy { path: shown, saved_to: None });
continue;
};
let base: PathBuf = match &directory {
Some(existing) => existing.clone(),
None => {
let created: PathBuf = match fresh_directory(saved_root.as_path(), stamp(milliseconds).as_str()) {
Ok(made) => made,
Err(error) => return Err(SaveFailure::io(saved_root.display().to_string(), error.to_string())),
};
directory = Some(created.clone());
created
}
};
let destination: PathBuf = base.join(OsString::from_vec(path.clone()));
if let Some(parent) = destination.parent() {
if let Err(error) = create_private_directory_all(parent) {
return Err(SaveFailure::io(shown, error.to_string()));
}
let mut up: Option<&Path> = Some(parent);
while let Some(each) = up {
written_directories.insert(each.to_path_buf());
if each == base.as_path() {
break;
}
up = each.parent();
}
}
copy_located(&located, destination.as_path(), &mut no_pause)?;
copies.push(SavedCopy { path: shown, saved_to: Some(destination) });
}
if directory.is_some() {
let mut ordered: Vec<&PathBuf> = written_directories.iter().collect();
ordered.sort_by_key(depth_deepest_first);
for each in ordered {
if let Err(error) = sync_directory(each.as_path()) {
return Err(SaveFailure::io(each.display().to_string(), error.to_string()));
}
}
let parent: PathBuf = common_dir.join("cli-git");
for each in [saved_root.as_path(), parent.as_path()] {
if let Err(error) = sync_directory(each) {
return Err(SaveFailure::io(each.display().to_string(), error.to_string()));
}
}
}
return Ok(copies);
}
#[cfg(test)]
#[path = "conflict_sides_tests.rs"]
mod tests;