Mercurial > hg
comparison rust/hg-cpython/src/copy_tracing.rs @ 45945:50c5ee3bdf9a
copies: introduce the hg-cpython wrapper for `combine_changeset_copies`
This patch focus on the `hg-cpython` part of this work. Bridging the python code
with the new rust code in `hg-core`. The next patch will actually plug this in
the python code.
The rust code use multiple Python callback, python related error within this
callback are not expected unless they are a programming error or a data
corruption. In addition, these callback will slowly be replaced by native Rust
code. For these reasons, we use will deal with unexpected error within this
callback using rust Panic and let the `rust-cpython` layer deal with raising a
Python exception.
The code dealing with the ChangedFile instance is repeating itself a lot. I did
not factor these duplication out because that whole code will get replaced by
entirely different one in a handful of changesets.
Differential Revision: https://phab.mercurial-scm.org/D9298
author | Pierre-Yves David <pierre-yves.david@octobus.net> |
---|---|
date | Thu, 01 Oct 2020 18:51:40 +0200 |
parents | |
children | e0313b0a6f7e |
comparison
equal
deleted
inserted
replaced
45944:595979dc924e | 45945:50c5ee3bdf9a |
---|---|
1 use cpython::ObjectProtocol; | |
2 use cpython::PyBool; | |
3 use cpython::PyBytes; | |
4 use cpython::PyDict; | |
5 use cpython::PyList; | |
6 use cpython::PyModule; | |
7 use cpython::PyObject; | |
8 use cpython::PyResult; | |
9 use cpython::PyTuple; | |
10 use cpython::Python; | |
11 | |
12 use hg::copy_tracing::combine_changeset_copies; | |
13 use hg::copy_tracing::ChangedFiles; | |
14 use hg::copy_tracing::RevInfo; | |
15 use hg::utils::hg_path::HgPathBuf; | |
16 use hg::Revision; | |
17 | |
18 /// Combines copies information contained into revision `revs` to build a copy | |
19 /// map. | |
20 /// | |
21 /// See mercurial/copies.py for details | |
22 pub fn combine_changeset_copies_wrapper( | |
23 py: Python, | |
24 revs: PyList, | |
25 children: PyDict, | |
26 target_rev: Revision, | |
27 rev_info: PyObject, | |
28 is_ancestor: PyObject, | |
29 ) -> PyResult<PyDict> { | |
30 let revs: PyResult<_> = | |
31 revs.iter(py).map(|r| Ok(r.extract(py)?)).collect(); | |
32 | |
33 // Wrap the `is_ancestor` python callback as a Rust closure | |
34 // | |
35 // No errors are expected from the Python side, and they will should only | |
36 // happens in case of programing error or severe data corruption. Such | |
37 // errors will raise panic and the rust-cpython harness will turn them into | |
38 // Python exception. | |
39 let is_ancestor_wrap = |anc: Revision, desc: Revision| -> bool { | |
40 is_ancestor | |
41 .call(py, (anc, desc), None) | |
42 .expect( | |
43 "rust-copy-tracing: python call to `is_ancestor` \ | |
44 failed", | |
45 ) | |
46 .cast_into::<PyBool>(py) | |
47 .expect( | |
48 "rust-copy-tracing: python call to `is_ancestor` \ | |
49 returned unexpected non-Bool value", | |
50 ) | |
51 .is_true() | |
52 }; | |
53 | |
54 // Wrap the `rev_info_maker` python callback as a Rust closure | |
55 // | |
56 // No errors are expected from the Python side, and they will should only | |
57 // happens in case of programing error or severe data corruption. Such | |
58 // errors will raise panic and the rust-cpython harness will turn them into | |
59 // Python exception. | |
60 let rev_info_maker = |rev: Revision| -> RevInfo { | |
61 let res: PyTuple = rev_info | |
62 .call(py, (rev,), None) | |
63 .expect("rust-copy-tracing: python call to `rev_info` failed") | |
64 .cast_into(py) | |
65 .expect( | |
66 "rust-copy_tracing: python call to `rev_info` returned \ | |
67 unexpected non-Tuple value", | |
68 ); | |
69 let p1 = res.get_item(py, 0).extract(py).expect( | |
70 "rust-copy-tracing: \ | |
71 rev_info return is invalid, first item is a not a revision", | |
72 ); | |
73 let p2 = res.get_item(py, 1).extract(py).expect( | |
74 "rust-copy-tracing: \ | |
75 rev_info return is invalid, second item is a not a revision", | |
76 ); | |
77 | |
78 let changes = res.get_item(py, 2); | |
79 | |
80 let files; | |
81 if !changes | |
82 .hasattr(py, "copied_from_p1") | |
83 .expect("rust-copy-tracing: python call to `hasattr` failed") | |
84 { | |
85 files = ChangedFiles::new_empty(); | |
86 } else { | |
87 let p1_copies: PyDict = changes | |
88 .getattr(py, "copied_from_p1") | |
89 .expect( | |
90 "rust-copy-tracing: retrieval of python attribute \ | |
91 `copied_from_p1` failed", | |
92 ) | |
93 .cast_into(py) | |
94 .expect( | |
95 "rust-copy-tracing: failed to convert `copied_from_p1` \ | |
96 to PyDict", | |
97 ); | |
98 let p1_copies: PyResult<_> = p1_copies | |
99 .items(py) | |
100 .iter() | |
101 .map(|(key, value)| { | |
102 let key = key.extract::<PyBytes>(py).expect( | |
103 "rust-copy-tracing: conversion of copy destination to\ | |
104 PyBytes failed", | |
105 ); | |
106 let key = key.data(py); | |
107 let value = value.extract::<PyBytes>(py).expect( | |
108 "rust-copy-tracing: conversion of copy source to \ | |
109 PyBytes failed", | |
110 ); | |
111 let value = value.data(py); | |
112 Ok(( | |
113 HgPathBuf::from_bytes(key), | |
114 HgPathBuf::from_bytes(value), | |
115 )) | |
116 }) | |
117 .collect(); | |
118 | |
119 let p2_copies: PyDict = changes | |
120 .getattr(py, "copied_from_p2") | |
121 .expect( | |
122 "rust-copy-tracing: retrieval of python attribute \ | |
123 `copied_from_p2` failed", | |
124 ) | |
125 .cast_into(py) | |
126 .expect( | |
127 "rust-copy-tracing: failed to convert `copied_from_p2` \ | |
128 to PyDict", | |
129 ); | |
130 let p2_copies: PyResult<_> = p2_copies | |
131 .items(py) | |
132 .iter() | |
133 .map(|(key, value)| { | |
134 let key = key.extract::<PyBytes>(py).expect( | |
135 "rust-copy-tracing: conversion of copy destination to \ | |
136 PyBytes failed"); | |
137 let key = key.data(py); | |
138 let value = value.extract::<PyBytes>(py).expect( | |
139 "rust-copy-tracing: conversion of copy source to \ | |
140 PyBytes failed", | |
141 ); | |
142 let value = value.data(py); | |
143 Ok(( | |
144 HgPathBuf::from_bytes(key), | |
145 HgPathBuf::from_bytes(value), | |
146 )) | |
147 }) | |
148 .collect(); | |
149 | |
150 let removed: PyObject = changes.getattr(py, "removed").expect( | |
151 "rust-copy-tracing: retrieval of python attribute \ | |
152 `removed` failed", | |
153 ); | |
154 let removed: PyResult<_> = removed | |
155 .iter(py) | |
156 .expect( | |
157 "rust-copy-tracing: getting a python iterator over the \ | |
158 `removed` set failed", | |
159 ) | |
160 .map(|filename| { | |
161 let filename = filename | |
162 .expect( | |
163 "rust-copy-tracing: python iteration over the \ | |
164 `removed` set failed", | |
165 ) | |
166 .extract::<PyBytes>(py) | |
167 .expect( | |
168 "rust-copy-tracing: \ | |
169 conversion of `removed` item to PyBytes failed", | |
170 ); | |
171 let filename = filename.data(py); | |
172 Ok(HgPathBuf::from_bytes(filename)) | |
173 }) | |
174 .collect(); | |
175 | |
176 let merged: PyObject = changes.getattr(py, "merged").expect( | |
177 "rust-copy-tracing: retrieval of python attribute \ | |
178 `merged` failed", | |
179 ); | |
180 let merged: PyResult<_> = merged | |
181 .iter(py) | |
182 .expect( | |
183 "rust-copy-tracing: getting a python iterator over the \ | |
184 `merged` set failed", | |
185 ) | |
186 .map(|filename| { | |
187 let filename = filename | |
188 .expect( | |
189 "rust-copy-tracing: python iteration over the \ | |
190 `merged` set failed", | |
191 ) | |
192 .extract::<PyBytes>(py) | |
193 .expect( | |
194 "rust-copy-tracing: \ | |
195 conversion of `merged` item to PyBytes failed", | |
196 ); | |
197 let filename = filename.data(py); | |
198 Ok(HgPathBuf::from_bytes(filename)) | |
199 }) | |
200 .collect(); | |
201 | |
202 let salvaged: PyObject = changes.getattr(py, "salvaged").expect( | |
203 "rust-copy-tracing: retrieval of python attribute \ | |
204 `salvaged` failed", | |
205 ); | |
206 let salvaged: PyResult<_> = salvaged | |
207 .iter(py) | |
208 .expect( | |
209 "rust-copy-tracing: getting a python iterator over the \ | |
210 `salvaged` set failed", | |
211 ) | |
212 .map(|filename| { | |
213 let filename = filename | |
214 .expect( | |
215 "rust-copy-tracing: python iteration over the \ | |
216 `salvaged` set failed", | |
217 ) | |
218 .extract::<PyBytes>(py) | |
219 .expect( | |
220 "rust-copy-tracing: \ | |
221 conversion of `salvaged` item to PyBytes failed", | |
222 ); | |
223 let filename = filename.data(py); | |
224 Ok(HgPathBuf::from_bytes(filename)) | |
225 }) | |
226 .collect(); | |
227 files = ChangedFiles::new( | |
228 removed.unwrap(), | |
229 merged.unwrap(), | |
230 salvaged.unwrap(), | |
231 p1_copies.unwrap(), | |
232 p2_copies.unwrap(), | |
233 ); | |
234 } | |
235 | |
236 (p1, p2, files) | |
237 }; | |
238 let children: PyResult<_> = children | |
239 .items(py) | |
240 .iter() | |
241 .map(|(k, v)| { | |
242 let v: &PyList = v.cast_as(py)?; | |
243 let v: PyResult<_> = | |
244 v.iter(py).map(|child| Ok(child.extract(py)?)).collect(); | |
245 Ok((k.extract(py)?, v?)) | |
246 }) | |
247 .collect(); | |
248 | |
249 let res = combine_changeset_copies( | |
250 revs?, | |
251 children?, | |
252 target_rev, | |
253 &rev_info_maker, | |
254 &is_ancestor_wrap, | |
255 ); | |
256 let out = PyDict::new(py); | |
257 for (dest, source) in res.into_iter() { | |
258 out.set_item( | |
259 py, | |
260 PyBytes::new(py, &dest.into_vec()), | |
261 PyBytes::new(py, &source.into_vec()), | |
262 )?; | |
263 } | |
264 Ok(out) | |
265 } | |
266 | |
267 /// Create the module, with `__package__` given from parent | |
268 pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> { | |
269 let dotted_name = &format!("{}.copy_tracing", package); | |
270 let m = PyModule::new(py, dotted_name)?; | |
271 | |
272 m.add(py, "__package__", package)?; | |
273 m.add(py, "__doc__", "Copy tracing - Rust implementation")?; | |
274 | |
275 m.add( | |
276 py, | |
277 "combine_changeset_copies", | |
278 py_fn!( | |
279 py, | |
280 combine_changeset_copies_wrapper( | |
281 revs: PyList, | |
282 children: PyDict, | |
283 target_rev: Revision, | |
284 rev_info: PyObject, | |
285 is_ancestor: PyObject | |
286 ) | |
287 ), | |
288 )?; | |
289 | |
290 let sys = PyModule::import(py, "sys")?; | |
291 let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?; | |
292 sys_modules.set_item(py, dotted_name, &m)?; | |
293 | |
294 Ok(m) | |
295 } |