use crate::diagnostic_log::{debug, warn};
use crate::fs_beneath::{ObjectId, handle_path, identity_at, identity_of, open_directory_at, remove_at};
use crate::fs_btrfs_ioctl::subvolume_create_at;
use crate::fs_kind::{FilesystemKind, filesystem_kind};
use crate::worktree_add_plan::AddPlan;
use std::path::{Path, PathBuf};
const TAG: &str = "worktreeSubvolume";
#[derive(Debug, Eq, PartialEq)]
pub struct CreatedSubvolume {
pub path: PathBuf,
pub identity: Option<ObjectId>,
}
pub fn prepare_subvolume_destination(plan: &AddPlan) -> Option<CreatedSubvolume> {
let destination: &Path = plan.destination.as_path();
if std::fs::symlink_metadata(destination).is_ok() {
debug(TAG, format!("{} exists already; Git decides whether it is acceptable", destination.display()).as_str());
return None;
}
let parent: &Path = destination.parent()?;
match filesystem_kind(parent) {
Ok(FilesystemKind::Btrfs) => {}
Ok(_) => return None,
Err(error) => {
debug(TAG, format!("the filesystem of {} is unknown ({error}); Git makes the directory", parent.display()).as_str());
return None;
}
}
if let Err(error) = subvolume_create_at(destination) {
debug(TAG, format!("no subvolume at {} ({error}); Git makes the directory", destination.display()).as_str());
return None;
}
debug(TAG, format!("created the empty subvolume {}", destination.display()).as_str());
let identity: Option<ObjectId> = std::fs::File::open(parent)
.and_then(|held: std::fs::File| { return identity_at(&held, destination.file_name().unwrap_or_default()); })
.inspect_err(|error: &std::io::Error| { debug(TAG, format!("the identity of the created subvolume {} cannot be read ({error}); settlement will leave it alone", destination.display()).as_str()); })
.ok();
return Some(CreatedSubvolume { path: destination.to_path_buf(), identity });
}
pub fn settle_subvolume_destination(created: &CreatedSubvolume) {
let shown: &Path = created.path.as_path();
let Some(expected) = created.identity else {
debug(TAG, format!("{} was not identified when it was created; left alone", shown.display()).as_str());
return;
};
let (Some(parent_path), Some(name)) = (shown.parent(), shown.file_name()) else {
debug(TAG, format!("{} has no parent and name; left alone", shown.display()).as_str());
return;
};
let parent: std::fs::File = match std::fs::File::open(parent_path) {
Ok(parent) => parent,
Err(error) => {
debug(TAG, format!("{} cannot be settled ({error}); left alone", parent_path.display()).as_str());
return;
}
};
let held: std::fs::File = match open_directory_at(&parent, name) {
Ok(held) => held,
Err(error) => {
debug(TAG, format!("{} cannot be opened ({error}); left alone", shown.display()).as_str());
return;
}
};
if identity_of(&held).ok() != Some(expected) {
warn(TAG, format!("{} is not the empty subvolume this command created, so it is left in place; remove it yourself if it is not wanted", shown.display()).as_str());
return;
}
let mut listing = match std::fs::read_dir(handle_path(&held)) {
Ok(listing) => listing,
Err(error) => {
debug(TAG, format!("{} cannot be listed ({error}); left alone", shown.display()).as_str());
return;
}
};
if listing.next().is_some() {
return;
}
match remove_at(&parent, name, true) {
Ok(()) => debug(TAG, format!("removed the empty subvolume {} that Git did not fill", shown.display()).as_str()),
Err(error) => debug(TAG, format!("could not remove the empty subvolume {} ({error})", shown.display()).as_str()),
}
}
#[cfg(test)]
#[path = "worktree_subvolume_tests.rs"]
mod tests;