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 cwd = path.parent().unwrap_or_else(|| Path::new("."));
patch_write(op, cwd, mode, guard)
}
#[cfg(any(feature = "cli", feature = "files"))]
fn patch_write(
op: Operation,
cwd: &Path,
mode: ApplyMode,
guard: Option<&PathGuard>,
) -> anyhow::Result<EditResult> {
super::execute_as_edit_result(op, mode, cwd, guard, "patch", None)
}
#[cfg(not(any(feature = "cli", feature = "files")))]
fn patch_write(
_op: Operation,
cwd: &Path,
mode: ApplyMode,
guard: Option<&PathGuard>,
) -> 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 results = 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),
})
}
})?;
let policy = crate::write::WritePolicy::default();
let (applied, backup_session) =
super::write_if_apply(&file_path, &new_content, mode, &policy, guard)?;
let mut edit =
super::build_edit_result(&pf.path, original, new_content, applied, "patch", None);
edit.backup_session = backup_session;
results.push(edit);
}
Ok(results)
}