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