Show More
@@ -1,183 +1,201 b'' | |||||
1 | // cindex.rs |
|
1 | // cindex.rs | |
2 | // |
|
2 | // | |
3 | // Copyright 2018 Georges Racinet <gracinet@anybox.fr> |
|
3 | // Copyright 2018 Georges Racinet <gracinet@anybox.fr> | |
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 to use the Index defined by the parsers C extension |
|
8 | //! Bindings to use the Index defined by the parsers C extension | |
9 | //! |
|
9 | //! | |
10 | //! Ideally, we should use an Index entirely implemented in Rust, |
|
10 | //! Ideally, we should use an Index entirely implemented in Rust, | |
11 | //! but this will take some time to get there. |
|
11 | //! but this will take some time to get there. | |
12 |
|
12 | |||
13 | use cpython::{ |
|
13 | use cpython::{ | |
14 | exc::ImportError, exc::TypeError, ObjectProtocol, PyClone, PyErr, |
|
14 | exc::ImportError, exc::TypeError, ObjectProtocol, PyClone, PyErr, | |
15 | PyObject, PyResult, PyTuple, Python, PythonObject, |
|
15 | PyObject, PyResult, PyTuple, Python, PythonObject, | |
16 | }; |
|
16 | }; | |
17 | use hg::revlog::{Node, RevlogIndex}; |
|
17 | use hg::revlog::{Node, RevlogIndex}; | |
18 | use hg::{Graph, GraphError, Revision, WORKING_DIRECTORY_REVISION}; |
|
18 | use hg::{Graph, GraphError, Revision, WORKING_DIRECTORY_REVISION}; | |
19 | use libc::{c_int, ssize_t}; |
|
19 | use libc::{c_int, ssize_t}; | |
20 |
|
20 | |||
21 | const REVLOG_CABI_VERSION: c_int = 2; |
|
21 | const REVLOG_CABI_VERSION: c_int = 2; | |
22 |
|
22 | |||
23 | #[repr(C)] |
|
23 | #[repr(C)] | |
24 | pub struct Revlog_CAPI { |
|
24 | pub struct Revlog_CAPI { | |
25 | abi_version: c_int, |
|
25 | abi_version: c_int, | |
26 | index_length: |
|
26 | index_length: | |
27 | unsafe extern "C" fn(index: *mut revlog_capi::RawPyObject) -> ssize_t, |
|
27 | unsafe extern "C" fn(index: *mut revlog_capi::RawPyObject) -> ssize_t, | |
28 | index_node: unsafe extern "C" fn( |
|
28 | index_node: unsafe extern "C" fn( | |
29 | index: *mut revlog_capi::RawPyObject, |
|
29 | index: *mut revlog_capi::RawPyObject, | |
30 | rev: ssize_t, |
|
30 | rev: ssize_t, | |
31 | ) -> *const Node, |
|
31 | ) -> *const Node, | |
32 | index_parents: unsafe extern "C" fn( |
|
32 | index_parents: unsafe extern "C" fn( | |
33 | index: *mut revlog_capi::RawPyObject, |
|
33 | index: *mut revlog_capi::RawPyObject, | |
34 | rev: c_int, |
|
34 | rev: c_int, | |
35 | ps: *mut [c_int; 2], |
|
35 | ps: *mut [c_int; 2], | |
36 | ) -> c_int, |
|
36 | ) -> c_int, | |
37 | } |
|
37 | } | |
38 |
|
38 | |||
39 | py_capsule!( |
|
39 | py_capsule!( | |
40 | from mercurial.cext.parsers import revlog_CAPI |
|
40 | from mercurial.cext.parsers import revlog_CAPI | |
41 | as revlog_capi for Revlog_CAPI); |
|
41 | as revlog_capi for Revlog_CAPI); | |
42 |
|
42 | |||
43 | /// A `Graph` backed up by objects and functions from revlog.c |
|
43 | /// A `Graph` backed up by objects and functions from revlog.c | |
44 | /// |
|
44 | /// | |
45 | /// This implementation of the `Graph` trait, relies on (pointers to) |
|
45 | /// This implementation of the `Graph` trait, relies on (pointers to) | |
46 | /// - the C index object (`index` member) |
|
46 | /// - the C index object (`index` member) | |
47 | /// - the `index_get_parents()` function (`parents` member) |
|
47 | /// - the `index_get_parents()` function (`parents` member) | |
48 | /// |
|
48 | /// | |
49 | /// # Safety |
|
49 | /// # Safety | |
50 | /// |
|
50 | /// | |
51 | /// The C index itself is mutable, and this Rust exposition is **not |
|
51 | /// The C index itself is mutable, and this Rust exposition is **not | |
52 | /// protected by the GIL**, meaning that this construct isn't safe with respect |
|
52 | /// protected by the GIL**, meaning that this construct isn't safe with respect | |
53 | /// to Python threads. |
|
53 | /// to Python threads. | |
54 | /// |
|
54 | /// | |
55 | /// All callers of this `Index` must acquire the GIL and must not release it |
|
55 | /// All callers of this `Index` must acquire the GIL and must not release it | |
56 | /// while working. |
|
56 | /// while working. | |
57 | /// |
|
57 | /// | |
58 | /// # TODO find a solution to make it GIL safe again. |
|
58 | /// # TODO find a solution to make it GIL safe again. | |
59 | /// |
|
59 | /// | |
60 | /// This is non trivial, and can wait until we have a clearer picture with |
|
60 | /// This is non trivial, and can wait until we have a clearer picture with | |
61 | /// more Rust Mercurial constructs. |
|
61 | /// more Rust Mercurial constructs. | |
62 | /// |
|
62 | /// | |
63 | /// One possibility would be to a `GILProtectedIndex` wrapper enclosing |
|
63 | /// One possibility would be to a `GILProtectedIndex` wrapper enclosing | |
64 | /// a `Python<'p>` marker and have it be the one implementing the |
|
64 | /// a `Python<'p>` marker and have it be the one implementing the | |
65 | /// `Graph` trait, but this would mean the `Graph` implementor would become |
|
65 | /// `Graph` trait, but this would mean the `Graph` implementor would become | |
66 | /// likely to change between subsequent method invocations of the `hg-core` |
|
66 | /// likely to change between subsequent method invocations of the `hg-core` | |
67 | /// objects (a serious change of the `hg-core` API): |
|
67 | /// objects (a serious change of the `hg-core` API): | |
68 | /// either exposing ways to mutate the `Graph`, or making it a non persistent |
|
68 | /// either exposing ways to mutate the `Graph`, or making it a non persistent | |
69 | /// parameter in the relevant methods that need one. |
|
69 | /// parameter in the relevant methods that need one. | |
70 | /// |
|
70 | /// | |
71 | /// Another possibility would be to introduce an abstract lock handle into |
|
71 | /// Another possibility would be to introduce an abstract lock handle into | |
72 | /// the core API, that would be tied to `GILGuard` / `Python<'p>` |
|
72 | /// the core API, that would be tied to `GILGuard` / `Python<'p>` | |
73 | /// in the case of the `cpython` crate bindings yet could leave room for other |
|
73 | /// in the case of the `cpython` crate bindings yet could leave room for other | |
74 | /// mechanisms in other contexts. |
|
74 | /// mechanisms in other contexts. | |
75 | pub struct Index { |
|
75 | pub struct Index { | |
76 | index: PyObject, |
|
76 | index: PyObject, | |
77 | capi: &'static Revlog_CAPI, |
|
77 | capi: &'static Revlog_CAPI, | |
78 | } |
|
78 | } | |
79 |
|
79 | |||
80 | impl Index { |
|
80 | impl Index { | |
81 | pub fn new(py: Python, index: PyObject) -> PyResult<Self> { |
|
81 | pub fn new(py: Python, index: PyObject) -> PyResult<Self> { | |
82 | let capi = unsafe { revlog_capi::retrieve(py)? }; |
|
82 | let capi = unsafe { revlog_capi::retrieve(py)? }; | |
83 | if capi.abi_version != REVLOG_CABI_VERSION { |
|
83 | if capi.abi_version != REVLOG_CABI_VERSION { | |
84 | return Err(PyErr::new::<ImportError, _>( |
|
84 | return Err(PyErr::new::<ImportError, _>( | |
85 | py, |
|
85 | py, | |
86 | format!( |
|
86 | format!( | |
87 | "ABI version mismatch: the C ABI revlog version {} \ |
|
87 | "ABI version mismatch: the C ABI revlog version {} \ | |
88 | does not match the {} expected by Rust hg-cpython", |
|
88 | does not match the {} expected by Rust hg-cpython", | |
89 | capi.abi_version, REVLOG_CABI_VERSION |
|
89 | capi.abi_version, REVLOG_CABI_VERSION | |
90 | ), |
|
90 | ), | |
91 | )); |
|
91 | )); | |
92 | } |
|
92 | } | |
93 | let compat: u64 = index.getattr(py, "rust_ext_compat")?.extract(py)?; |
|
93 | let compat: u64 = index.getattr(py, "rust_ext_compat")?.extract(py)?; | |
94 | if compat == 0 { |
|
94 | if compat == 0 { | |
95 | return Err(PyErr::new::<TypeError, _>( |
|
95 | return Err(PyErr::new::<TypeError, _>( | |
96 | py, |
|
96 | py, | |
97 | "index object not compatible with Rust", |
|
97 | "index object not compatible with Rust", | |
98 | )); |
|
98 | )); | |
99 | } |
|
99 | } | |
100 | Ok(Index { index, capi }) |
|
100 | Ok(Index { index, capi }) | |
101 | } |
|
101 | } | |
102 |
|
102 | |||
103 | /// return a reference to the CPython Index object in this Struct |
|
103 | /// return a reference to the CPython Index object in this Struct | |
104 | pub fn inner(&self) -> &PyObject { |
|
104 | pub fn inner(&self) -> &PyObject { | |
105 | &self.index |
|
105 | &self.index | |
106 | } |
|
106 | } | |
107 |
|
107 | |||
108 | pub fn append(&mut self, py: Python, tup: PyTuple) -> PyResult<PyObject> { |
|
108 | pub fn append(&mut self, py: Python, tup: PyTuple) -> PyResult<PyObject> { | |
109 | self.index.call_method( |
|
109 | self.index.call_method( | |
110 | py, |
|
110 | py, | |
111 | "append", |
|
111 | "append", | |
112 | PyTuple::new(py, &[tup.into_object()]), |
|
112 | PyTuple::new(py, &[tup.into_object()]), | |
113 | None, |
|
113 | None, | |
114 | ) |
|
114 | ) | |
115 | } |
|
115 | } | |
116 | } |
|
116 | } | |
117 |
|
117 | |||
118 | impl Clone for Index { |
|
118 | impl Clone for Index { | |
119 | fn clone(&self) -> Self { |
|
119 | fn clone(&self) -> Self { | |
120 | let guard = Python::acquire_gil(); |
|
120 | let guard = Python::acquire_gil(); | |
121 | Index { |
|
121 | Index { | |
122 | index: self.index.clone_ref(guard.python()), |
|
122 | index: self.index.clone_ref(guard.python()), | |
123 | capi: self.capi, |
|
123 | capi: self.capi, | |
124 | } |
|
124 | } | |
125 | } |
|
125 | } | |
126 | } |
|
126 | } | |
127 |
|
127 | |||
128 | impl PyClone for Index { |
|
128 | impl PyClone for Index { | |
129 | fn clone_ref(&self, py: Python) -> Self { |
|
129 | fn clone_ref(&self, py: Python) -> Self { | |
130 | Index { |
|
130 | Index { | |
131 | index: self.index.clone_ref(py), |
|
131 | index: self.index.clone_ref(py), | |
132 | capi: self.capi, |
|
132 | capi: self.capi, | |
133 | } |
|
133 | } | |
134 | } |
|
134 | } | |
135 | } |
|
135 | } | |
136 |
|
136 | |||
137 | impl Graph for Index { |
|
137 | impl Graph for Index { | |
138 | /// wrap a call to the C extern parents function |
|
138 | /// wrap a call to the C extern parents function | |
139 | fn parents(&self, rev: Revision) -> Result<[Revision; 2], GraphError> { |
|
139 | fn parents(&self, rev: Revision) -> Result<[Revision; 2], GraphError> { | |
140 | if rev == WORKING_DIRECTORY_REVISION { |
|
140 | if rev == WORKING_DIRECTORY_REVISION { | |
141 | return Err(GraphError::WorkingDirectoryUnsupported); |
|
141 | return Err(GraphError::WorkingDirectoryUnsupported); | |
142 | } |
|
142 | } | |
143 | let mut res: [c_int; 2] = [0; 2]; |
|
143 | let mut res: [c_int; 2] = [0; 2]; | |
144 | let code = unsafe { |
|
144 | let code = unsafe { | |
145 | (self.capi.index_parents)( |
|
145 | (self.capi.index_parents)( | |
146 | self.index.as_ptr(), |
|
146 | self.index.as_ptr(), | |
147 | rev as c_int, |
|
147 | rev as c_int, | |
148 | &mut res as *mut [c_int; 2], |
|
148 | &mut res as *mut [c_int; 2], | |
149 | ) |
|
149 | ) | |
150 | }; |
|
150 | }; | |
151 | match code { |
|
151 | match code { | |
152 | 0 => Ok(res), |
|
152 | 0 => Ok(res), | |
153 | _ => Err(GraphError::ParentOutOfRange(rev)), |
|
153 | _ => Err(GraphError::ParentOutOfRange(rev)), | |
154 | } |
|
154 | } | |
155 | } |
|
155 | } | |
156 | } |
|
156 | } | |
157 |
|
157 | |||
|
158 | impl vcsgraph::graph::Graph for Index { | |||
|
159 | fn parents( | |||
|
160 | &self, | |||
|
161 | rev: Revision, | |||
|
162 | ) -> Result<vcsgraph::graph::Parents, vcsgraph::graph::GraphReadError> | |||
|
163 | { | |||
|
164 | match Graph::parents(self, rev) { | |||
|
165 | Ok(parents) => Ok(vcsgraph::graph::Parents(parents)), | |||
|
166 | Err(GraphError::ParentOutOfRange(rev)) => { | |||
|
167 | Err(vcsgraph::graph::GraphReadError::KeyedInvalidKey(rev)) | |||
|
168 | } | |||
|
169 | Err(GraphError::WorkingDirectoryUnsupported) => Err( | |||
|
170 | vcsgraph::graph::GraphReadError::WorkingDirectoryUnsupported, | |||
|
171 | ), | |||
|
172 | } | |||
|
173 | } | |||
|
174 | } | |||
|
175 | ||||
158 | impl RevlogIndex for Index { |
|
176 | impl RevlogIndex for Index { | |
159 | /// Note C return type is Py_ssize_t (hence signed), but we shall |
|
177 | /// Note C return type is Py_ssize_t (hence signed), but we shall | |
160 | /// force it to unsigned, because it's a length |
|
178 | /// force it to unsigned, because it's a length | |
161 | fn len(&self) -> usize { |
|
179 | fn len(&self) -> usize { | |
162 | unsafe { (self.capi.index_length)(self.index.as_ptr()) as usize } |
|
180 | unsafe { (self.capi.index_length)(self.index.as_ptr()) as usize } | |
163 | } |
|
181 | } | |
164 |
|
182 | |||
165 | fn node(&self, rev: Revision) -> Option<&Node> { |
|
183 | fn node(&self, rev: Revision) -> Option<&Node> { | |
166 | let raw = unsafe { |
|
184 | let raw = unsafe { | |
167 | (self.capi.index_node)(self.index.as_ptr(), rev as ssize_t) |
|
185 | (self.capi.index_node)(self.index.as_ptr(), rev as ssize_t) | |
168 | }; |
|
186 | }; | |
169 | if raw.is_null() { |
|
187 | if raw.is_null() { | |
170 | None |
|
188 | None | |
171 | } else { |
|
189 | } else { | |
172 | // TODO it would be much better for the C layer to give us |
|
190 | // TODO it would be much better for the C layer to give us | |
173 | // a length, since the hash length will change in the near |
|
191 | // a length, since the hash length will change in the near | |
174 | // future, but that's probably out of scope for the nodemap |
|
192 | // future, but that's probably out of scope for the nodemap | |
175 | // patch series. |
|
193 | // patch series. | |
176 | // |
|
194 | // | |
177 | // The root of that unsafety relies in the signature of |
|
195 | // The root of that unsafety relies in the signature of | |
178 | // `capi.index_node()` itself: returning a `Node` pointer |
|
196 | // `capi.index_node()` itself: returning a `Node` pointer | |
179 | // whereas it's a `char *` in the C counterpart. |
|
197 | // whereas it's a `char *` in the C counterpart. | |
180 | Some(unsafe { &*raw }) |
|
198 | Some(unsafe { &*raw }) | |
181 | } |
|
199 | } | |
182 | } |
|
200 | } | |
183 | } |
|
201 | } |
General Comments 0
You need to be logged in to leave comments.
Login now