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 }