##// END OF EJS Templates
rust-dirstate: create dirstate submodule in hg-cpython...
Raphaël Gomès -
r42973:85cb54e7 default draft
parent child Browse files
Show More
@@ -0,0 +1,110 b''
1 // dirs_multiset.rs
2 //
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 //
5 // This software may be used and distributed according to the terms of the
6 // GNU General Public License version 2 or any later version.
7
8 //! Bindings for the `hg::dirstate::dirs_multiset` file provided by the
9 //! `hg-core` package.
10
11 use std::cell::RefCell;
12
13 use cpython::{
14 exc, ObjectProtocol, PyBytes, PyDict, PyErr, PyObject, PyResult,
15 ToPyObject,
16 };
17
18 use crate::dirstate::extract_dirstate_vec;
19 use hg::{DirsIterable, DirsMultiset, DirstateMapError};
20
21 py_class!(pub class Dirs |py| {
22 data dirs_map: RefCell<DirsMultiset>;
23
24 // `map` is either a `dict` or a flat iterator (usually a `set`, sometimes
25 // a `list`)
26 def __new__(
27 _cls,
28 map: PyObject,
29 skip: Option<PyObject> = None
30 ) -> PyResult<Self> {
31 let mut skip_state: Option<i8> = None;
32 if let Some(skip) = skip {
33 skip_state = Some(skip.extract::<PyBytes>(py)?.data(py)[0] as i8);
34 }
35 let dirs_map;
36
37 if let Ok(map) = map.cast_as::<PyDict>(py) {
38 let dirstate_vec = extract_dirstate_vec(py, &map)?;
39 dirs_map = DirsMultiset::new(
40 DirsIterable::Dirstate(dirstate_vec),
41 skip_state,
42 )
43 } else {
44 let map: Result<Vec<Vec<u8>>, PyErr> = map
45 .iter(py)?
46 .map(|o| Ok(o?.extract::<PyBytes>(py)?.data(py).to_owned()))
47 .collect();
48 dirs_map = DirsMultiset::new(
49 DirsIterable::Manifest(map?),
50 skip_state,
51 )
52 }
53
54 Self::create_instance(py, RefCell::new(dirs_map))
55 }
56
57 def addpath(&self, path: PyObject) -> PyResult<PyObject> {
58 self.dirs_map(py).borrow_mut().add_path(
59 path.extract::<PyBytes>(py)?.data(py),
60 );
61 Ok(py.None())
62 }
63
64 def delpath(&self, path: PyObject) -> PyResult<PyObject> {
65 self.dirs_map(py).borrow_mut().delete_path(
66 path.extract::<PyBytes>(py)?.data(py),
67 )
68 .and(Ok(py.None()))
69 .or_else(|e| {
70 match e {
71 DirstateMapError::PathNotFound(_p) => {
72 Err(PyErr::new::<exc::ValueError, _>(
73 py,
74 "expected a value, found none".to_string(),
75 ))
76 }
77 DirstateMapError::EmptyPath => {
78 Ok(py.None())
79 }
80 }
81 })
82 }
83
84 // This is really inefficient on top of being ugly, but it's an easy way
85 // of having it work to continue working on the rest of the module
86 // hopefully bypassing Python entirely pretty soon.
87 def __iter__(&self) -> PyResult<PyObject> {
88 let dict = PyDict::new(py);
89
90 for (key, value) in self.dirs_map(py).borrow().iter() {
91 dict.set_item(
92 py,
93 PyBytes::new(py, &key[..]),
94 value.to_py_object(py),
95 )?;
96 }
97
98 let locals = PyDict::new(py);
99 locals.set_item(py, "obj", dict)?;
100
101 py.eval("iter(obj)", None, Some(&locals))
102 }
103
104 def __contains__(&self, item: PyObject) -> PyResult<bool> {
105 Ok(self
106 .dirs_map(py)
107 .borrow()
108 .contains_key(item.extract::<PyBytes>(py)?.data(py).as_ref()))
109 }
110 });
@@ -1,331 +1,239 b''
1 // dirstate.rs
1 // dirstate.rs
2 //
2 //
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 //
4 //
5 // This software may be used and distributed according to the terms of the
5 // This software may be used and distributed according to the terms of the
6 // GNU General Public License version 2 or any later version.
6 // GNU General Public License version 2 or any later version.
7
7
8 //! Bindings for the `hg::dirstate` module provided by the
8 //! Bindings for the `hg::dirstate` module provided by the
9 //! `hg-core` package.
9 //! `hg-core` package.
10 //!
10 //!
11 //! From Python, this will be seen as `mercurial.rustext.dirstate`
11 //! From Python, this will be seen as `mercurial.rustext.dirstate`
12
12 mod dirs_multiset;
13 use crate::dirstate::dirs_multiset::Dirs;
13 use cpython::{
14 use cpython::{
14 exc, ObjectProtocol, PyBytes, PyDict, PyErr, PyInt, PyModule, PyObject,
15 exc, PyBytes, PyDict, PyErr, PyInt, PyModule, PyObject, PyResult,
15 PyResult, PySequence, PyTuple, Python, PythonObject, ToPyObject,
16 PySequence, PyTuple, Python, PythonObject, ToPyObject,
16 };
17 };
17 use hg::{
18 use hg::{
18 pack_dirstate, parse_dirstate, CopyVecEntry, DirsIterable, DirsMultiset,
19 pack_dirstate, parse_dirstate, CopyVecEntry, DirstateEntry,
19 DirstateEntry, DirstateMapError, DirstatePackError, DirstateParents,
20 DirstatePackError, DirstateParents, DirstateParseError, DirstateVec,
20 DirstateParseError, DirstateVec,
21 };
21 };
22 use libc::{c_char, c_int};
22 use libc::{c_char, c_int};
23 #[cfg(feature = "python27")]
23 #[cfg(feature = "python27")]
24 use python27_sys::PyCapsule_Import;
24 use python27_sys::PyCapsule_Import;
25 #[cfg(feature = "python3")]
25 #[cfg(feature = "python3")]
26 use python3_sys::PyCapsule_Import;
26 use python3_sys::PyCapsule_Import;
27 use std::cell::RefCell;
28 use std::collections::HashMap;
27 use std::collections::HashMap;
29 use std::ffi::CStr;
28 use std::ffi::CStr;
30 use std::mem::transmute;
29 use std::mem::transmute;
31
30
32 /// C code uses a custom `dirstate_tuple` type, checks in multiple instances
31 /// C code uses a custom `dirstate_tuple` type, checks in multiple instances
33 /// for this type, and raises a Python `Exception` if the check does not pass.
32 /// for this type, and raises a Python `Exception` if the check does not pass.
34 /// Because this type differs only in name from the regular Python tuple, it
33 /// Because this type differs only in name from the regular Python tuple, it
35 /// would be a good idea in the near future to remove it entirely to allow
34 /// would be a good idea in the near future to remove it entirely to allow
36 /// for a pure Python tuple of the same effective structure to be used,
35 /// for a pure Python tuple of the same effective structure to be used,
37 /// rendering this type and the capsule below useless.
36 /// rendering this type and the capsule below useless.
38 type MakeDirstateTupleFn = extern "C" fn(
37 type MakeDirstateTupleFn = extern "C" fn(
39 state: c_char,
38 state: c_char,
40 mode: c_int,
39 mode: c_int,
41 size: c_int,
40 size: c_int,
42 mtime: c_int,
41 mtime: c_int,
43 ) -> PyObject;
42 ) -> PyObject;
44
43
45 /// This is largely a copy/paste from cindex.rs, pending the merge of a
44 /// This is largely a copy/paste from cindex.rs, pending the merge of a
46 /// `py_capsule_fn!` macro in the rust-cpython project:
45 /// `py_capsule_fn!` macro in the rust-cpython project:
47 /// https://github.com/dgrunwald/rust-cpython/pull/169
46 /// https://github.com/dgrunwald/rust-cpython/pull/169
48 fn decapsule_make_dirstate_tuple(py: Python) -> PyResult<MakeDirstateTupleFn> {
47 fn decapsule_make_dirstate_tuple(py: Python) -> PyResult<MakeDirstateTupleFn> {
49 unsafe {
48 unsafe {
50 let caps_name = CStr::from_bytes_with_nul_unchecked(
49 let caps_name = CStr::from_bytes_with_nul_unchecked(
51 b"mercurial.cext.parsers.make_dirstate_tuple_CAPI\0",
50 b"mercurial.cext.parsers.make_dirstate_tuple_CAPI\0",
52 );
51 );
53 let from_caps = PyCapsule_Import(caps_name.as_ptr(), 0);
52 let from_caps = PyCapsule_Import(caps_name.as_ptr(), 0);
54 if from_caps.is_null() {
53 if from_caps.is_null() {
55 return Err(PyErr::fetch(py));
54 return Err(PyErr::fetch(py));
56 }
55 }
57 Ok(transmute(from_caps))
56 Ok(transmute(from_caps))
58 }
57 }
59 }
58 }
60
59
61 fn parse_dirstate_wrapper(
60 fn parse_dirstate_wrapper(
62 py: Python,
61 py: Python,
63 dmap: PyDict,
62 dmap: PyDict,
64 copymap: PyDict,
63 copymap: PyDict,
65 st: PyBytes,
64 st: PyBytes,
66 ) -> PyResult<PyTuple> {
65 ) -> PyResult<PyTuple> {
67 match parse_dirstate(st.data(py)) {
66 match parse_dirstate(st.data(py)) {
68 Ok((parents, dirstate_vec, copies)) => {
67 Ok((parents, dirstate_vec, copies)) => {
69 for (filename, entry) in dirstate_vec {
68 for (filename, entry) in dirstate_vec {
70 dmap.set_item(
69 dmap.set_item(
71 py,
70 py,
72 PyBytes::new(py, &filename[..]),
71 PyBytes::new(py, &filename[..]),
73 decapsule_make_dirstate_tuple(py)?(
72 decapsule_make_dirstate_tuple(py)?(
74 entry.state as c_char,
73 entry.state as c_char,
75 entry.mode,
74 entry.mode,
76 entry.size,
75 entry.size,
77 entry.mtime,
76 entry.mtime,
78 ),
77 ),
79 )?;
78 )?;
80 }
79 }
81 for CopyVecEntry { path, copy_path } in copies {
80 for CopyVecEntry { path, copy_path } in copies {
82 copymap.set_item(
81 copymap.set_item(
83 py,
82 py,
84 PyBytes::new(py, path),
83 PyBytes::new(py, path),
85 PyBytes::new(py, copy_path),
84 PyBytes::new(py, copy_path),
86 )?;
85 )?;
87 }
86 }
88 Ok((PyBytes::new(py, parents.p1), PyBytes::new(py, parents.p2))
87 Ok((PyBytes::new(py, parents.p1), PyBytes::new(py, parents.p2))
89 .to_py_object(py))
88 .to_py_object(py))
90 }
89 }
91 Err(e) => Err(PyErr::new::<exc::ValueError, _>(
90 Err(e) => Err(PyErr::new::<exc::ValueError, _>(
92 py,
91 py,
93 match e {
92 match e {
94 DirstateParseError::TooLittleData => {
93 DirstateParseError::TooLittleData => {
95 "too little data for parents".to_string()
94 "too little data for parents".to_string()
96 }
95 }
97 DirstateParseError::Overflow => {
96 DirstateParseError::Overflow => {
98 "overflow in dirstate".to_string()
97 "overflow in dirstate".to_string()
99 }
98 }
100 DirstateParseError::CorruptedEntry(e) => e,
99 DirstateParseError::CorruptedEntry(e) => e,
101 },
100 },
102 )),
101 )),
103 }
102 }
104 }
103 }
105
104
106 fn extract_dirstate_vec(
105 fn extract_dirstate_vec(
107 py: Python,
106 py: Python,
108 dmap: &PyDict,
107 dmap: &PyDict,
109 ) -> Result<DirstateVec, PyErr> {
108 ) -> Result<DirstateVec, PyErr> {
110 dmap.items(py)
109 dmap.items(py)
111 .iter()
110 .iter()
112 .map(|(filename, stats)| {
111 .map(|(filename, stats)| {
113 let stats = stats.extract::<PySequence>(py)?;
112 let stats = stats.extract::<PySequence>(py)?;
114 let state = stats.get_item(py, 0)?.extract::<PyBytes>(py)?;
113 let state = stats.get_item(py, 0)?.extract::<PyBytes>(py)?;
115 let state = state.data(py)[0] as i8;
114 let state = state.data(py)[0] as i8;
116 let mode = stats.get_item(py, 1)?.extract(py)?;
115 let mode = stats.get_item(py, 1)?.extract(py)?;
117 let size = stats.get_item(py, 2)?.extract(py)?;
116 let size = stats.get_item(py, 2)?.extract(py)?;
118 let mtime = stats.get_item(py, 3)?.extract(py)?;
117 let mtime = stats.get_item(py, 3)?.extract(py)?;
119 let filename = filename.extract::<PyBytes>(py)?;
118 let filename = filename.extract::<PyBytes>(py)?;
120 let filename = filename.data(py);
119 let filename = filename.data(py);
121 Ok((
120 Ok((
122 filename.to_owned(),
121 filename.to_owned(),
123 DirstateEntry {
122 DirstateEntry {
124 state,
123 state,
125 mode,
124 mode,
126 size,
125 size,
127 mtime,
126 mtime,
128 },
127 },
129 ))
128 ))
130 })
129 })
131 .collect()
130 .collect()
132 }
131 }
133
132
134 fn pack_dirstate_wrapper(
133 fn pack_dirstate_wrapper(
135 py: Python,
134 py: Python,
136 dmap: PyDict,
135 dmap: PyDict,
137 copymap: PyDict,
136 copymap: PyDict,
138 pl: PyTuple,
137 pl: PyTuple,
139 now: PyInt,
138 now: PyInt,
140 ) -> PyResult<PyBytes> {
139 ) -> PyResult<PyBytes> {
141 let p1 = pl.get_item(py, 0).extract::<PyBytes>(py)?;
140 let p1 = pl.get_item(py, 0).extract::<PyBytes>(py)?;
142 let p1: &[u8] = p1.data(py);
141 let p1: &[u8] = p1.data(py);
143 let p2 = pl.get_item(py, 1).extract::<PyBytes>(py)?;
142 let p2 = pl.get_item(py, 1).extract::<PyBytes>(py)?;
144 let p2: &[u8] = p2.data(py);
143 let p2: &[u8] = p2.data(py);
145
144
146 let dirstate_vec = extract_dirstate_vec(py, &dmap)?;
145 let dirstate_vec = extract_dirstate_vec(py, &dmap)?;
147
146
148 let copies: Result<HashMap<Vec<u8>, Vec<u8>>, PyErr> = copymap
147 let copies: Result<HashMap<Vec<u8>, Vec<u8>>, PyErr> = copymap
149 .items(py)
148 .items(py)
150 .iter()
149 .iter()
151 .map(|(key, value)| {
150 .map(|(key, value)| {
152 Ok((
151 Ok((
153 key.extract::<PyBytes>(py)?.data(py).to_owned(),
152 key.extract::<PyBytes>(py)?.data(py).to_owned(),
154 value.extract::<PyBytes>(py)?.data(py).to_owned(),
153 value.extract::<PyBytes>(py)?.data(py).to_owned(),
155 ))
154 ))
156 })
155 })
157 .collect();
156 .collect();
158
157
159 match pack_dirstate(
158 match pack_dirstate(
160 &dirstate_vec,
159 &dirstate_vec,
161 &copies?,
160 &copies?,
162 DirstateParents { p1, p2 },
161 DirstateParents { p1, p2 },
163 now.as_object().extract::<i32>(py)?,
162 now.as_object().extract::<i32>(py)?,
164 ) {
163 ) {
165 Ok((packed, new_dirstate_vec)) => {
164 Ok((packed, new_dirstate_vec)) => {
166 for (
165 for (
167 filename,
166 filename,
168 DirstateEntry {
167 DirstateEntry {
169 state,
168 state,
170 mode,
169 mode,
171 size,
170 size,
172 mtime,
171 mtime,
173 },
172 },
174 ) in new_dirstate_vec
173 ) in new_dirstate_vec
175 {
174 {
176 dmap.set_item(
175 dmap.set_item(
177 py,
176 py,
178 PyBytes::new(py, &filename[..]),
177 PyBytes::new(py, &filename[..]),
179 decapsule_make_dirstate_tuple(py)?(
178 decapsule_make_dirstate_tuple(py)?(
180 state as c_char,
179 state as c_char,
181 mode,
180 mode,
182 size,
181 size,
183 mtime,
182 mtime,
184 ),
183 ),
185 )?;
184 )?;
186 }
185 }
187 Ok(PyBytes::new(py, &packed))
186 Ok(PyBytes::new(py, &packed))
188 }
187 }
189 Err(error) => Err(PyErr::new::<exc::ValueError, _>(
188 Err(error) => Err(PyErr::new::<exc::ValueError, _>(
190 py,
189 py,
191 match error {
190 match error {
192 DirstatePackError::CorruptedParent => {
191 DirstatePackError::CorruptedParent => {
193 "expected a 20-byte hash".to_string()
192 "expected a 20-byte hash".to_string()
194 }
193 }
195 DirstatePackError::CorruptedEntry(e) => e,
194 DirstatePackError::CorruptedEntry(e) => e,
196 DirstatePackError::BadSize(expected, actual) => {
195 DirstatePackError::BadSize(expected, actual) => {
197 format!("bad dirstate size: {} != {}", actual, expected)
196 format!("bad dirstate size: {} != {}", actual, expected)
198 }
197 }
199 },
198 },
200 )),
199 )),
201 }
200 }
202 }
201 }
203
202
204 py_class!(pub class Dirs |py| {
205 data dirs_map: RefCell<DirsMultiset>;
206
207 // `map` is either a `dict` or a flat iterator (usually a `set`, sometimes
208 // a `list`)
209 def __new__(
210 _cls,
211 map: PyObject,
212 skip: Option<PyObject> = None
213 ) -> PyResult<Self> {
214 let mut skip_state: Option<i8> = None;
215 if let Some(skip) = skip {
216 skip_state = Some(skip.extract::<PyBytes>(py)?.data(py)[0] as i8);
217 }
218 let dirs_map;
219
220 if let Ok(map) = map.cast_as::<PyDict>(py) {
221 let dirstate_vec = extract_dirstate_vec(py, &map)?;
222 dirs_map = DirsMultiset::new(
223 DirsIterable::Dirstate(dirstate_vec),
224 skip_state,
225 )
226 } else {
227 let map: Result<Vec<Vec<u8>>, PyErr> = map
228 .iter(py)?
229 .map(|o| Ok(o?.extract::<PyBytes>(py)?.data(py).to_owned()))
230 .collect();
231 dirs_map = DirsMultiset::new(
232 DirsIterable::Manifest(map?),
233 skip_state,
234 )
235 }
236
237 Self::create_instance(py, RefCell::new(dirs_map))
238 }
239
240 def addpath(&self, path: PyObject) -> PyResult<PyObject> {
241 self.dirs_map(py).borrow_mut().add_path(
242 path.extract::<PyBytes>(py)?.data(py),
243 );
244 Ok(py.None())
245 }
246
247 def delpath(&self, path: PyObject) -> PyResult<PyObject> {
248 self.dirs_map(py).borrow_mut().delete_path(
249 path.extract::<PyBytes>(py)?.data(py),
250 )
251 .and(Ok(py.None()))
252 .or_else(|e| {
253 match e {
254 DirstateMapError::PathNotFound(_p) => {
255 Err(PyErr::new::<exc::ValueError, _>(
256 py,
257 "expected a value, found none".to_string(),
258 ))
259 }
260 DirstateMapError::EmptyPath => {
261 Ok(py.None())
262 }
263 }
264 })
265 }
266
267 // This is really inefficient on top of being ugly, but it's an easy way
268 // of having it work to continue working on the rest of the module
269 // hopefully bypassing Python entirely pretty soon.
270 def __iter__(&self) -> PyResult<PyObject> {
271 let dict = PyDict::new(py);
272
273 for (key, value) in self.dirs_map(py).borrow().iter() {
274 dict.set_item(
275 py,
276 PyBytes::new(py, &key[..]),
277 value.to_py_object(py),
278 )?;
279 }
280
281 let locals = PyDict::new(py);
282 locals.set_item(py, "obj", dict)?;
283
284 py.eval("iter(obj)", None, Some(&locals))
285 }
286
287 def __contains__(&self, item: PyObject) -> PyResult<bool> {
288 Ok(self
289 .dirs_map(py)
290 .borrow()
291 .contains_key(item.extract::<PyBytes>(py)?.data(py).as_ref()))
292 }
293 });
294
295 /// Create the module, with `__package__` given from parent
203 /// Create the module, with `__package__` given from parent
296 pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
204 pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
297 let dotted_name = &format!("{}.dirstate", package);
205 let dotted_name = &format!("{}.dirstate", package);
298 let m = PyModule::new(py, dotted_name)?;
206 let m = PyModule::new(py, dotted_name)?;
299
207
300 m.add(py, "__package__", package)?;
208 m.add(py, "__package__", package)?;
301 m.add(py, "__doc__", "Dirstate - Rust implementation")?;
209 m.add(py, "__doc__", "Dirstate - Rust implementation")?;
302 m.add(
210 m.add(
303 py,
211 py,
304 "parse_dirstate",
212 "parse_dirstate",
305 py_fn!(
213 py_fn!(
306 py,
214 py,
307 parse_dirstate_wrapper(dmap: PyDict, copymap: PyDict, st: PyBytes)
215 parse_dirstate_wrapper(dmap: PyDict, copymap: PyDict, st: PyBytes)
308 ),
216 ),
309 )?;
217 )?;
310 m.add(
218 m.add(
311 py,
219 py,
312 "pack_dirstate",
220 "pack_dirstate",
313 py_fn!(
221 py_fn!(
314 py,
222 py,
315 pack_dirstate_wrapper(
223 pack_dirstate_wrapper(
316 dmap: PyDict,
224 dmap: PyDict,
317 copymap: PyDict,
225 copymap: PyDict,
318 pl: PyTuple,
226 pl: PyTuple,
319 now: PyInt
227 now: PyInt
320 )
228 )
321 ),
229 ),
322 )?;
230 )?;
323
231
324 m.add_class::<Dirs>(py)?;
232 m.add_class::<Dirs>(py)?;
325
233
326 let sys = PyModule::import(py, "sys")?;
234 let sys = PyModule::import(py, "sys")?;
327 let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
235 let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
328 sys_modules.set_item(py, dotted_name, &m)?;
236 sys_modules.set_item(py, dotted_name, &m)?;
329
237
330 Ok(m)
238 Ok(m)
331 }
239 }
General Comments 0
You need to be logged in to leave comments. Login now