use super::{TxState, read_file_content, update_file_content};
use crate::plan::Operation;
pub(crate) fn execute_file_op(op: &Operation, tx: &mut TxState<'_>) -> anyhow::Result<usize> {
match op {
Operation::FileAppend { path, content } => {
let file_path = tx.cwd.join(path);
if file_path.exists() && !file_path.is_file() {
anyhow::bail!("target is not a file: {path}");
}
if tx.deletions.contains(&file_path) {
anyhow::bail!("file was deleted earlier in this transaction: {path}");
}
if !file_path.exists() && !tx.pending.contains_key(&file_path) {
anyhow::bail!("file does not exist: {path}");
}
let existing = read_file_content(tx.pending, tx.existed_before, &file_path)?;
let combined = crate::ops::file::append_content(existing, content);
update_file_content(
tx.pending,
tx.deletions,
tx.write_targets,
&file_path,
combined,
);
}
Operation::FilePrepend { path, content } => {
let file_path = tx.cwd.join(path);
if file_path.exists() && !file_path.is_file() {
anyhow::bail!("target is not a file: {path}");
}
if tx.deletions.contains(&file_path) {
anyhow::bail!("file was deleted earlier in this transaction: {path}");
}
if !file_path.exists() && !tx.pending.contains_key(&file_path) {
anyhow::bail!("file does not exist: {path}");
}
let existing = read_file_content(tx.pending, tx.existed_before, &file_path)?;
let combined = crate::ops::file::prepend_content(existing, content);
update_file_content(
tx.pending,
tx.deletions,
tx.write_targets,
&file_path,
combined,
);
}
Operation::FileCreate {
path,
content,
force,
} => {
let file_path = tx.cwd.join(path);
if file_path.exists() && !file_path.is_file() {
anyhow::bail!("target is not a file: {path}");
}
if force.unwrap_or(false) {
if tx.pending.contains_key(&file_path) || file_path.exists() {
let _ = read_file_content(tx.pending, tx.existed_before, &file_path)?;
}
update_file_content(
tx.pending,
tx.deletions,
tx.write_targets,
&file_path,
content.clone(),
);
} else {
let exists_in_tx =
tx.pending.contains_key(&file_path) && !tx.deletions.contains(&file_path);
if exists_in_tx || (!tx.deletions.contains(&file_path) && file_path.exists()) {
anyhow::bail!("file already exists: {path}");
}
update_file_content(
tx.pending,
tx.deletions,
tx.write_targets,
&file_path,
content.clone(),
);
}
}
Operation::FileDelete { path } => {
let file_path = tx.cwd.join(path);
if file_path.exists() && !file_path.is_file() {
anyhow::bail!("target is not a file: {path}");
}
let created_in_tx = match tx.pending.get(&file_path) {
Some((original, _)) => original.is_empty() && !file_path.exists(),
None => {
if !file_path.exists() {
anyhow::bail!("file not found: {path}");
}
tx.existed_before.insert(file_path.clone());
match std::fs::read_to_string(&file_path) {
Ok(content) => {
tx.pending
.insert(file_path.clone(), (content.clone(), content));
}
Err(_) => {
tx.pending
.insert(file_path.clone(), (String::new(), String::new()));
}
}
false
}
};
if created_in_tx {
tx.pending.remove(&file_path);
tx.deletions.remove(&file_path);
} else {
update_file_content(
tx.pending,
tx.deletions,
tx.write_targets,
&file_path,
String::new(),
);
tx.deletions.insert(file_path);
}
}
Operation::FileRename { from, to, force } => {
let src_path = tx.cwd.join(from);
let dst_path = tx.cwd.join(to);
if tx.deletions.contains(&src_path) {
anyhow::bail!("source file was deleted earlier in this transaction: {from}");
}
if src_path.exists() && !src_path.is_file() {
anyhow::bail!("source is not a file: {from}");
}
if dst_path.exists() && !dst_path.is_file() {
anyhow::bail!("destination is not a file: {to}");
}
let case_only = src_path != dst_path
&& src_path.parent() == dst_path.parent()
&& src_path.file_name().map(|n| n.to_ascii_lowercase())
== dst_path.file_name().map(|n| n.to_ascii_lowercase());
if !case_only
&& (src_path == dst_path
|| matches!(
(src_path.canonicalize(), dst_path.canonicalize()),
(Ok(ref s), Ok(ref d)) if s == d
))
{
return Ok(0);
}
let content = read_file_content(tx.pending, tx.existed_before, &src_path)?.to_string();
if !force && !case_only {
let dst_exists = (tx.pending.contains_key(&dst_path)
&& !tx.deletions.contains(&dst_path))
|| (!tx.deletions.contains(&dst_path) && dst_path.exists());
if dst_exists {
anyhow::bail!("destination already exists: {to}");
}
}
if (*force || case_only) && !tx.pending.contains_key(&dst_path) && dst_path.exists() {
let _ = read_file_content(tx.pending, tx.existed_before, &dst_path)?;
}
update_file_content(
tx.pending,
tx.deletions,
tx.write_targets,
&dst_path,
content,
);
let created_in_tx = match tx.pending.get(&src_path) {
Some((original, _)) => original.is_empty() && !src_path.exists(),
None => false,
};
if created_in_tx {
tx.pending.remove(&src_path);
tx.deletions.remove(&src_path);
} else {
update_file_content(
tx.pending,
tx.deletions,
tx.write_targets,
&src_path,
String::new(),
);
tx.deletions.insert(src_path);
}
}
_ => anyhow::bail!("execute_file_op called with non-file operation"),
}
Ok(0)
}