##// END OF EJS Templates
hg-cpython: implement vcsgraph::Graph for our Index...
pacien -
r49349:8e8737a1 default
parent child Browse files
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