1use crate::transform::TreeTransform;
3use patchkit::unified::{HunkLine, UnifiedPatch};
4use pyo3::intern;
5use pyo3::prelude::*;
6use pyo3::types::{PyBytes, PyList};
7
8fn py_patches(iter_patches: impl Iterator<Item = UnifiedPatch>) -> PyResult<Py<PyAny>> {
9 Python::attach(|py| {
10 let m = py.import("breezy.patches")?;
11 let patchc = m.getattr("Patch")?;
12 let hunkc = m.getattr("Hunk")?;
13 let insertlinec = m.getattr("InsertLine")?;
14 let removelinec = m.getattr("RemoveLine")?;
15 let contextlinec = m.getattr("ContextLine")?;
16 let mut ret = vec![];
17 for patch in iter_patches {
18 let pypatch = patchc.call1((
19 PyBytes::new(py, &patch.orig_name),
20 PyBytes::new(py, &patch.mod_name),
21 patch.orig_ts,
22 patch.mod_ts,
23 ))?;
24 let pyhunks = pypatch.getattr("hunks")?;
25
26 for hunk in patch.hunks {
27 let pyhunk = hunkc.call1((
28 hunk.orig_pos,
29 hunk.orig_range,
30 hunk.mod_pos,
31 hunk.mod_range,
32 hunk.tail,
33 ))?;
34 pyhunks.call_method1("append", (&pyhunk,))?;
35
36 let pylines = pyhunk.getattr("lines")?;
37
38 for line in hunk.lines {
39 pylines.call_method1(
40 "append",
41 (match line {
42 HunkLine::ContextLine(l) => {
43 contextlinec.call1((PyBytes::new(py, l.as_slice()),))?
44 }
45 HunkLine::InsertLine(l) => {
46 insertlinec.call1((PyBytes::new(py, l.as_slice()),))?
47 }
48 HunkLine::RemoveLine(l) => {
49 removelinec.call1((PyBytes::new(py, l.as_slice()),))?
50 }
51 },),
52 )?;
53 }
54 }
55 ret.push(pypatch);
56 }
57 Ok(PyList::new(py, ret.iter())?.unbind().into())
58 })
59}
60
61pub fn apply_patches(
68 tt: &TreeTransform,
69 patches: impl Iterator<Item = UnifiedPatch>,
70 prefix: Option<usize>,
71) -> crate::Result<()> {
72 Python::attach(|py| {
73 let patches = py_patches(patches)?;
74 let m = py.import("breezy.tree")?;
75 let apply_patches = m.getattr("apply_patches")?;
76 apply_patches.call1((tt.as_pyobject(), patches, prefix))?;
77 Ok(())
78 })
79}
80
81pub struct AppliedPatches(Py<PyAny>, Py<PyAny>);
86
87impl AppliedPatches {
88 pub fn new<T: crate::tree::PyTree>(
100 tree: &T,
101 patches: Vec<UnifiedPatch>,
102 prefix: Option<usize>,
103 ) -> crate::Result<Self> {
104 let (ap, tree) = Python::attach(|py| -> Result<_, PyErr> {
105 let patches = py_patches(patches.into_iter())?;
106 let m = py.import("breezy.patches")?;
107 let c = m.getattr("AppliedPatches")?;
108 let ap = c.call1((tree.to_object(py), patches, prefix))?;
109 let tree = ap.call_method0(intern!(py, "__enter__"))?;
110 Ok((ap.unbind(), tree.unbind()))
111 })?;
112 Ok(Self(tree, ap))
113 }
114}
115
116impl Drop for AppliedPatches {
117 fn drop(&mut self) {
118 Python::attach(|py| -> Result<(), PyErr> {
119 self.1.call_method1(
120 py,
121 intern!(py, "__exit__"),
122 (py.None(), py.None(), py.None()),
123 )?;
124 Ok(())
125 })
126 .unwrap();
127 }
128}
129
130impl<'py> IntoPyObject<'py> for AppliedPatches {
131 type Target = PyAny;
132 type Output = Bound<'py, Self::Target>;
133 type Error = std::convert::Infallible;
134
135 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
136 Ok(self.0.clone_ref(py).into_bound(py))
137 }
138}
139
140impl crate::tree::PyTree for AppliedPatches {
141 fn to_object(&self, py: Python) -> Py<PyAny> {
142 self.0.clone_ref(py)
143 }
144}
145
146#[cfg(test)]
147mod applied_patches_tests {
148 use super::*;
149 use crate::controldir::ControlDirFormat;
150 use crate::tree::MutableTree;
151 use crate::tree::Tree;
152 use crate::workingtree::WorkingTree;
153 use serial_test::serial;
154
155 #[test]
156 #[serial]
157 fn test_apply_simple() {
158 let env = crate::testing::TestEnv::new();
159 let td = tempfile::tempdir().unwrap();
160 let tree = crate::controldir::create_standalone_workingtree(
161 td.path(),
162 &ControlDirFormat::default(),
163 )
164 .unwrap();
165 std::fs::write(td.path().join("a"), "a\n").unwrap();
166 tree.add(&[std::path::Path::new("a")]).unwrap();
167 tree.build_commit()
168 .message("Add a")
169 .reporter(&crate::commit::NullCommitReporter::new())
170 .commit()
171 .unwrap();
172 let patch = UnifiedPatch::parse_patch(patchkit::unified::splitlines(
173 br#"--- a/a
174+++ b/a
175@@ -1 +1 @@
176-a
177+b
178"#,
179 ))
180 .unwrap();
181
182 let newtree = crate::patches::AppliedPatches::new(&tree, vec![patch], None).unwrap();
183 assert_eq!(
184 b"b\n".to_vec(),
185 newtree.get_file_text(std::path::Path::new("a")).unwrap()
186 );
187 std::mem::drop(newtree);
188 std::mem::drop(env);
189 }
190
191 #[test]
192 #[serial]
193 fn test_apply_delete() {
194 let env = crate::testing::TestEnv::new();
195 let td = tempfile::tempdir().unwrap();
196 let tree = crate::controldir::create_standalone_workingtree(
197 td.path(),
198 &ControlDirFormat::default(),
199 )
200 .unwrap();
201 std::fs::write(td.path().join("a"), "a\n").unwrap();
202 tree.add(&[std::path::Path::new("a")]).unwrap();
203 tree.build_commit()
204 .reporter(&crate::commit::NullCommitReporter::new())
205 .message("Add a")
206 .commit()
207 .unwrap();
208 let patch = UnifiedPatch::parse_patch(patchkit::unified::splitlines(
209 br#"--- a/a
210+++ /dev/null
211@@ -1 +0,0 @@
212-a
213"#,
214 ))
215 .unwrap();
216 let newtree = crate::patches::AppliedPatches::new(&tree, vec![patch], None).unwrap();
217 assert!(!newtree.has_filename(std::path::Path::new("a")));
218 std::mem::drop(env);
219 }
220
221 #[test]
222 #[serial]
223 fn test_apply_add() {
224 let env = crate::testing::TestEnv::new();
225 let td = tempfile::tempdir().unwrap();
226 let tree = crate::controldir::create_standalone_workingtree(
227 td.path(),
228 &ControlDirFormat::default(),
229 )
230 .unwrap();
231 std::fs::write(td.path().join("a"), "a\n").unwrap();
232 tree.add(&[std::path::Path::new("a")]).unwrap();
233 tree.build_commit()
234 .reporter(&crate::commit::NullCommitReporter::new())
235 .message("Add a")
236 .commit()
237 .unwrap();
238 let patch = UnifiedPatch::parse_patch(patchkit::unified::splitlines(
239 br#"--- /dev/null
240+++ b/b
241@@ -0,0 +1 @@
242+b
243"#,
244 ))
245 .unwrap();
246 let newtree = crate::patches::AppliedPatches::new(&tree, vec![patch], None).unwrap();
247 assert_eq!(
248 b"b\n".to_vec(),
249 newtree.get_file_text(std::path::Path::new("b")).unwrap()
250 );
251 std::mem::drop(env);
252 }
253}