1use std::fs;
16use std::path::Path;
17use std::process::ExitCode;
18
19use diffy::apply_bytes;
20use diffy::binary::BinaryPatch;
21use diffy::patch_set::FileOperation;
22use diffy::patch_set::ParseOptions;
23use diffy::patch_set::PatchKind;
24use diffy::patch_set::PatchSet;
25
26fn main() -> ExitCode {
27 let args: Vec<String> = std::env::args().collect();
28 if args.len() < 2 || args.len() > 3 {
29 eprintln!("usage: {} <patch-file> [target-dir]", args[0]);
30 return ExitCode::FAILURE;
31 }
32 let patch_file = Path::new(&args[1]);
33 let target_dir = args.get(2).map_or_else(|| Path::new("."), |p| Path::new(p));
34
35 if let Err(e) = apply_patch_file(patch_file, target_dir) {
36 eprintln!("error: {e}");
37 return ExitCode::FAILURE;
38 }
39 ExitCode::SUCCESS
40}
41
42fn apply_patch_file(patch_file: &Path, dst: &Path) -> Result<(), Box<dyn std::error::Error>> {
43 let content = fs::read(patch_file)?;
44
45 let patches = PatchSet::parse_bytes(&content, ParseOptions::gitdiff());
46
47 for file_patch in patches {
48 let file_patch = file_patch?;
49 let operation = {
50 let op = file_patch.operation();
51 let strip = match op {
53 FileOperation::Rename { .. } | FileOperation::Copy { .. } => 0,
54 _ => 1,
55 };
56 op.strip_prefix(strip)
57 };
58
59 match operation {
60 FileOperation::Create(path) => {
61 let target = dst.join(path_from_bytes(&path)?);
62 let patched = match file_patch.patch() {
63 PatchKind::Text(patch) => apply_bytes(&[], patch)?,
64 PatchKind::Binary(BinaryPatch::Marker) => continue,
65 PatchKind::Binary(patch) => patch.apply(&[])?,
66 };
67 create_parent_dirs(&target)?;
68 fs::write(&target, patched)?;
69 eprintln!("create {}", target.display());
70 }
71 FileOperation::Delete(path) => {
72 let target = dst.join(path_from_bytes(&path)?);
73 fs::remove_file(&target)?;
74 eprintln!("delete {}", target.display());
75 }
76 FileOperation::Modify { original, modified } => {
77 let src_path = dst.join(path_from_bytes(&original)?);
78 let dst_path = dst.join(path_from_bytes(&modified)?);
79 let patched = match file_patch.patch() {
80 PatchKind::Text(patch) => {
81 let base = fs::read(&src_path)?;
82 apply_bytes(&base, patch)?
83 }
84 PatchKind::Binary(BinaryPatch::Marker) => continue,
85 PatchKind::Binary(patch) => {
86 let base = fs::read(&src_path)?;
87 patch.apply(&base)?
88 }
89 };
90 create_parent_dirs(&dst_path)?;
91 fs::write(&dst_path, patched)?;
92 if src_path != dst_path {
93 fs::remove_file(&src_path)?;
94 eprintln!("rename {} -> {}", src_path.display(), dst_path.display());
95 } else {
96 eprintln!("modify {}", dst_path.display());
97 }
98 }
99 FileOperation::Rename { from, to } => {
100 let src_path = dst.join(path_from_bytes(&from)?);
101 let dst_path = dst.join(path_from_bytes(&to)?);
102 create_parent_dirs(&dst_path)?;
103 fs::rename(&src_path, &dst_path)?;
104 eprintln!("rename {} -> {}", src_path.display(), dst_path.display());
105 }
106 FileOperation::Copy { from, to } => {
107 let src_path = dst.join(path_from_bytes(&from)?);
108 let dst_path = dst.join(path_from_bytes(&to)?);
109 create_parent_dirs(&dst_path)?;
110 fs::copy(&src_path, &dst_path)?;
111 eprintln!("copy {} -> {}", src_path.display(), dst_path.display());
112 }
113 }
114 }
115
116 Ok(())
117}
118
119#[cfg(unix)]
120fn path_from_bytes(bytes: &[u8]) -> Result<&Path, Box<dyn std::error::Error>> {
121 use std::os::unix::ffi::OsStrExt;
122 Ok(Path::new(std::ffi::OsStr::from_bytes(bytes)))
123}
124
125#[cfg(not(unix))]
126fn path_from_bytes(bytes: &[u8]) -> Result<&Path, Box<dyn std::error::Error>> {
127 Ok(Path::new(std::str::from_utf8(bytes)?))
128}
129
130fn create_parent_dirs(path: &Path) -> std::io::Result<()> {
131 if let Some(parent) = path.parent() {
132 fs::create_dir_all(parent)?;
133 }
134 Ok(())
135}