1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
use crate::error::PatchError;
use patch::Patch;
use std::{error::Error, fmt::Display, path::PathBuf};
pub struct PatchOptions {
pub line_ending: String,
pub work_directory: PathBuf,
}
impl Default for PatchOptions {
fn default() -> Self {
Self {
line_ending: if std::env::consts::OS == "windows" {
"\r\n".to_string()
} else {
"\n".to_string()
},
work_directory: PathBuf::from(""),
}
}
}
impl Display for PatchError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::ParseError(err) => format!("ParseError::ParseError: {}", err),
Self::IOError(err) => format!("PatchError::FileError: {}", err),
Self::Unknown => "PatchError::Unknown: Unknown error.".to_string(),
}
)
}
}
impl Error for PatchError {}
pub fn apply_patch(patch: &str, options: PatchOptions) -> Result<(), PatchError> {
let workdir = options.work_directory;
let patch = if patch.ends_with(&options.line_ending) {
patch.to_string()
} else {
patch.to_string() + &options.line_ending
};
match Patch::from_multiple(&patch) {
Ok(patches) => {
for patch in patches {
match std::fs::read_to_string(
workdir.join(
patch
.old
.path
.trim()
.split_ascii_whitespace()
.next()
.unwrap()
.to_string(),
),
) {
Ok(old) => {
let mut hunks = patch.hunks.into_iter();
let old_lines: Vec<_> = old.split(&options.line_ending).collect();
let mut new_lines = Vec::<String>::new();
let mut old_line_num = 1usize;
while let Some(hunk) = hunks.next() {
if old_line_num < hunk.old_range.start as usize {
new_lines.extend(
old_lines[(old_line_num as usize - 1)
..hunk.old_range.start as usize]
.iter()
.map(|x| x.to_string()),
);
}
old_line_num = hunk.old_range.start as usize;
let mut patch_lines = hunk.lines.into_iter();
while let Some(patch_line) = patch_lines.next() {
match patch_line {
patch::Line::Remove(_) => {
old_line_num += 1;
}
patch::Line::Add(add_line) => {
new_lines.push(add_line.to_string());
}
patch::Line::Context(_) => {
new_lines.push(old_lines[old_line_num - 1].to_string());
old_line_num += 1;
}
}
}
}
for i in old_line_num.. {
if i < old_lines.len() {
new_lines.push(old_lines[i].to_string());
} else {
break;
}
}
let new = new_lines.join(&options.line_ending);
match std::fs::write(
workdir.join(
patch
.new
.path
.to_string()
.split_ascii_whitespace()
.next()
.unwrap(),
),
new,
) {
Ok(_) => {}
Err(_) => return Err(PatchError::Unknown),
}
}
Err(err) => return Err(PatchError::IOError(err)),
}
}
Ok(())
}
Err(err) => Err(PatchError::ParseError(err.to_string())),
}
}