use std::path::Path;
use crate::containment::PathGuard;
use crate::plan::Operation;
use super::{ApplyMode, EditResult};
pub fn apply_patch(
path: &Path,
patch_text: &str,
mode: ApplyMode,
guard: Option<&PathGuard>,
) -> anyhow::Result<EditResult> {
let op = Operation::PatchApply {
diff: patch_text.into(),
on_stale: Default::default(),
allow_conflicts: false,
};
let abs = super::absolute_for_engine(path).map_err(|e| {
crate::fallback::EditError::new(
crate::fallback::EditErrorKind::OperationFailed,
format!("failed to resolve path {}: {e}", path.display()),
)
})?;
let cwd_owned: std::path::PathBuf;
let cwd = if path.is_absolute() {
abs.parent().unwrap_or_else(|| Path::new("."))
} else {
cwd_owned = abs
.ancestors()
.nth(path.components().count())
.unwrap_or_else(|| Path::new("."))
.to_path_buf();
cwd_owned.as_path()
};
let display = path.to_string_lossy();
patch_write(op, cwd, mode, guard, Some(display.as_ref()))
}
#[cfg(any(feature = "cli", feature = "files"))]
fn patch_write(
op: Operation,
cwd: &Path,
mode: ApplyMode,
guard: Option<&PathGuard>,
display_path: Option<&str>,
) -> anyhow::Result<EditResult> {
super::execute_as_edit_result_with_path(op, mode, cwd, guard, "patch", None, display_path)
}
#[cfg(not(any(feature = "cli", feature = "files")))]
fn patch_write(
_op: Operation,
cwd: &Path,
mode: ApplyMode,
guard: Option<&PathGuard>,
_display_path: Option<&str>,
) -> anyhow::Result<EditResult> {
use crate::ops;
if let Operation::PatchApply { diff, .. } = _op {
let patch_files = ops::patch::parse_patch(&diff).map_err(|e| {
anyhow::Error::new(crate::exit::ParseErrorError {
msg: format!("patch parse error: {e}"),
})
})?;
if patch_files.is_empty() {
return Err(anyhow::Error::new(crate::exit::ParseErrorError {
msg: "no files in patch".into(),
}));
}
let pf = &patch_files[0];
let file_path = cwd.join(&pf.path);
let original = crate::files::load_text_strict(&file_path, &pf.path)?;
let new_content = ops::patch::apply_hunks(&original, &pf.hunks).map_err(|e| {
if e.contains("stale context") {
anyhow::Error::new(crate::exit::AmbiguousError {
msg: format!("patch apply error: {e}"),
})
} else {
anyhow::Error::new(crate::exit::InvalidInputError {
msg: format!("patch apply error: {e}"),
})
}
})?;
let policy = crate::write::WritePolicy::default();
let (applied, backup_session) =
super::write_if_apply(&file_path, &new_content, mode, &policy, guard)?;
{
let mut __e =
super::build_edit_result(&pf.path, original, new_content, applied, "patch", None);
__e.backup_session = backup_session;
Ok(__e)
}
} else {
anyhow::bail!("expected PatchApply operation")
}
}
pub fn apply_patch_file(
patch_text: &str,
cwd: &Path,
mode: ApplyMode,
guard: Option<&PathGuard>,
) -> anyhow::Result<Vec<EditResult>> {
let patch_files = crate::ops::patch::parse_patch(patch_text).map_err(|e| {
anyhow::Error::new(crate::exit::ParseErrorError {
msg: format!("patch parse error: {e}"),
})
})?;
let mut staged: Vec<(std::path::PathBuf, String, String, String)> = Vec::new();
for pf in &patch_files {
let file_path = cwd.join(&pf.path);
let original = crate::files::load_text_strict(&file_path, &pf.path)?;
let new_content = crate::ops::patch::apply_hunks(&original, &pf.hunks).map_err(|e| {
if e.contains("stale context") {
anyhow::Error::new(crate::exit::AmbiguousError {
msg: format!("patch apply error for {}: {e}", pf.path),
})
} else {
anyhow::Error::new(crate::exit::InvalidInputError {
msg: format!("patch apply error for {}: {e}", pf.path),
})
}
})?;
staged.push((file_path, pf.path.clone(), original, new_content));
}
let policy = crate::write::WritePolicy::default();
let write_pairs: Vec<(&std::path::Path, &str)> = staged
.iter()
.map(|(p, _, _, n)| (p.as_path(), n.as_str()))
.collect();
let (applied, backup_session) =
super::write_if_apply_many(&write_pairs, mode, &policy, guard, cwd)?;
let mut results = Vec::with_capacity(staged.len());
for (_abs, display, original, new_content) in staged {
let mut edit =
super::build_edit_result(&display, original, new_content, applied, "patch", None);
edit.backup_session = backup_session.clone();
results.push(edit);
}
Ok(results)
}