##// END OF EJS Templates
rust-index: make it possible to clone the struct referencing the C index...
Georges Racinet -
r44462:2728fcb8 default
parent child Browse files
Show More
@@ -1,110 +1,119
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::{PyClone, PyObject, PyResult, Python};
13 use cpython::{PyClone, PyObject, PyResult, Python};
14 use hg::{Graph, GraphError, Revision, WORKING_DIRECTORY_REVISION};
14 use hg::{Graph, GraphError, Revision, WORKING_DIRECTORY_REVISION};
15 use libc::c_int;
15 use libc::c_int;
16
16
17 #[repr(C)]
17 #[repr(C)]
18 pub struct Revlog_CAPI {
18 pub struct Revlog_CAPI {
19 index_parents: unsafe extern "C" fn(
19 index_parents: unsafe extern "C" fn(
20 index: *mut revlog_capi::RawPyObject,
20 index: *mut revlog_capi::RawPyObject,
21 rev: c_int,
21 rev: c_int,
22 ps: *mut [c_int; 2],
22 ps: *mut [c_int; 2],
23 ) -> c_int,
23 ) -> c_int,
24 }
24 }
25
25
26 py_capsule!(
26 py_capsule!(
27 from mercurial.cext.parsers import revlog_CAPI
27 from mercurial.cext.parsers import revlog_CAPI
28 as revlog_capi for Revlog_CAPI);
28 as revlog_capi for Revlog_CAPI);
29
29
30 /// A `Graph` backed up by objects and functions from revlog.c
30 /// A `Graph` backed up by objects and functions from revlog.c
31 ///
31 ///
32 /// This implementation of the `Graph` trait, relies on (pointers to)
32 /// This implementation of the `Graph` trait, relies on (pointers to)
33 /// - the C index object (`index` member)
33 /// - the C index object (`index` member)
34 /// - the `index_get_parents()` function (`parents` member)
34 /// - the `index_get_parents()` function (`parents` member)
35 ///
35 ///
36 /// # Safety
36 /// # Safety
37 ///
37 ///
38 /// The C index itself is mutable, and this Rust exposition is **not
38 /// The C index itself is mutable, and this Rust exposition is **not
39 /// protected by the GIL**, meaning that this construct isn't safe with respect
39 /// protected by the GIL**, meaning that this construct isn't safe with respect
40 /// to Python threads.
40 /// to Python threads.
41 ///
41 ///
42 /// All callers of this `Index` must acquire the GIL and must not release it
42 /// All callers of this `Index` must acquire the GIL and must not release it
43 /// while working.
43 /// while working.
44 ///
44 ///
45 /// # TODO find a solution to make it GIL safe again.
45 /// # TODO find a solution to make it GIL safe again.
46 ///
46 ///
47 /// This is non trivial, and can wait until we have a clearer picture with
47 /// This is non trivial, and can wait until we have a clearer picture with
48 /// more Rust Mercurial constructs.
48 /// more Rust Mercurial constructs.
49 ///
49 ///
50 /// One possibility would be to a `GILProtectedIndex` wrapper enclosing
50 /// One possibility would be to a `GILProtectedIndex` wrapper enclosing
51 /// a `Python<'p>` marker and have it be the one implementing the
51 /// a `Python<'p>` marker and have it be the one implementing the
52 /// `Graph` trait, but this would mean the `Graph` implementor would become
52 /// `Graph` trait, but this would mean the `Graph` implementor would become
53 /// likely to change between subsequent method invocations of the `hg-core`
53 /// likely to change between subsequent method invocations of the `hg-core`
54 /// objects (a serious change of the `hg-core` API):
54 /// objects (a serious change of the `hg-core` API):
55 /// either exposing ways to mutate the `Graph`, or making it a non persistent
55 /// either exposing ways to mutate the `Graph`, or making it a non persistent
56 /// parameter in the relevant methods that need one.
56 /// parameter in the relevant methods that need one.
57 ///
57 ///
58 /// Another possibility would be to introduce an abstract lock handle into
58 /// Another possibility would be to introduce an abstract lock handle into
59 /// the core API, that would be tied to `GILGuard` / `Python<'p>`
59 /// the core API, that would be tied to `GILGuard` / `Python<'p>`
60 /// in the case of the `cpython` crate bindings yet could leave room for other
60 /// in the case of the `cpython` crate bindings yet could leave room for other
61 /// mechanisms in other contexts.
61 /// mechanisms in other contexts.
62 pub struct Index {
62 pub struct Index {
63 index: PyObject,
63 index: PyObject,
64 capi: &'static Revlog_CAPI,
64 capi: &'static Revlog_CAPI,
65 }
65 }
66
66
67 impl Index {
67 impl Index {
68 pub fn new(py: Python, index: PyObject) -> PyResult<Self> {
68 pub fn new(py: Python, index: PyObject) -> PyResult<Self> {
69 Ok(Index {
69 Ok(Index {
70 index: index,
70 index: index,
71 capi: unsafe { revlog_capi::retrieve(py)? },
71 capi: unsafe { revlog_capi::retrieve(py)? },
72 })
72 })
73 }
73 }
74
74
75 /// return a reference to the CPython Index object in this Struct
75 /// return a reference to the CPython Index object in this Struct
76 pub fn inner(&self) -> &PyObject {
76 pub fn inner(&self) -> &PyObject {
77 &self.index
77 &self.index
78 }
78 }
79 }
79 }
80
80
81 impl Clone for Index {
81 impl Clone for Index {
82 fn clone(&self) -> Self {
82 fn clone(&self) -> Self {
83 let guard = Python::acquire_gil();
83 let guard = Python::acquire_gil();
84 Index {
84 Index {
85 index: self.index.clone_ref(guard.python()),
85 index: self.index.clone_ref(guard.python()),
86 capi: self.capi,
86 capi: self.capi,
87 }
87 }
88 }
88 }
89 }
89 }
90
90
91 impl PyClone for Index {
92 fn clone_ref(&self, py: Python) -> Self {
93 Index {
94 index: self.index.clone_ref(py),
95 capi: self.capi,
96 }
97 }
98 }
99
91 impl Graph for Index {
100 impl Graph for Index {
92 /// wrap a call to the C extern parents function
101 /// wrap a call to the C extern parents function
93 fn parents(&self, rev: Revision) -> Result<[Revision; 2], GraphError> {
102 fn parents(&self, rev: Revision) -> Result<[Revision; 2], GraphError> {
94 if rev == WORKING_DIRECTORY_REVISION {
103 if rev == WORKING_DIRECTORY_REVISION {
95 return Err(GraphError::WorkingDirectoryUnsupported);
104 return Err(GraphError::WorkingDirectoryUnsupported);
96 }
105 }
97 let mut res: [c_int; 2] = [0; 2];
106 let mut res: [c_int; 2] = [0; 2];
98 let code = unsafe {
107 let code = unsafe {
99 (self.capi.index_parents)(
108 (self.capi.index_parents)(
100 self.index.as_ptr(),
109 self.index.as_ptr(),
101 rev as c_int,
110 rev as c_int,
102 &mut res as *mut [c_int; 2],
111 &mut res as *mut [c_int; 2],
103 )
112 )
104 };
113 };
105 match code {
114 match code {
106 0 => Ok(res),
115 0 => Ok(res),
107 _ => Err(GraphError::ParentOutOfRange(rev)),
116 _ => Err(GraphError::ParentOutOfRange(rev)),
108 }
117 }
109 }
118 }
110 }
119 }
@@ -1,217 +1,218
1 // revlog.rs
1 // revlog.rs
2 //
2 //
3 // Copyright 2019 Georges Racinet <georges.racinet@octobus.net>
3 // Copyright 2019 Georges Racinet <georges.racinet@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 use crate::cindex;
8 use crate::cindex;
9 use cpython::{
9 use cpython::{
10 ObjectProtocol, PyDict, PyModule, PyObject, PyResult, PyTuple, Python,
10 ObjectProtocol, PyClone, PyDict, PyModule, PyObject, PyResult, PyTuple, Python, PythonObject,
11 PythonObject, ToPyObject,
11 ToPyObject,
12 };
12 };
13 use hg::Revision;
13 use hg::Revision;
14 use std::cell::RefCell;
14 use std::cell::RefCell;
15
15
16 /// Return a Struct implementing the Graph trait
16 /// Return a Struct implementing the Graph trait
17 pub(crate) fn pyindex_to_graph(
17 pub(crate) fn pyindex_to_graph(py: Python, index: PyObject) -> PyResult<cindex::Index> {
18 py: Python,
19 index: PyObject,
20 ) -> PyResult<cindex::Index> {
21 cindex::Index::new(py, index)
18 cindex::Index::new(py, index)
22 }
19 }
23
20
24 py_class!(pub class MixedIndex |py| {
21 py_class!(pub class MixedIndex |py| {
25 data cindex: RefCell<cindex::Index>;
22 data cindex: RefCell<cindex::Index>;
26
23
27 def __new__(_cls, cindex: PyObject) -> PyResult<MixedIndex> {
24 def __new__(_cls, cindex: PyObject) -> PyResult<MixedIndex> {
28 Self::create_instance(py, RefCell::new(
25 Self::create_instance(py, RefCell::new(
29 cindex::Index::new(py, cindex)?))
26 cindex::Index::new(py, cindex)?))
30 }
27 }
31
28
32
29
33 // Reforwarded C index API
30 // Reforwarded C index API
34
31
35 // index_methods (tp_methods). Same ordering as in revlog.c
32 // index_methods (tp_methods). Same ordering as in revlog.c
36
33
37 /// return the gca set of the given revs
34 /// return the gca set of the given revs
38 def ancestors(&self, *args, **kw) -> PyResult<PyObject> {
35 def ancestors(&self, *args, **kw) -> PyResult<PyObject> {
39 self.call_cindex(py, "ancestors", args, kw)
36 self.call_cindex(py, "ancestors", args, kw)
40 }
37 }
41
38
42 /// return the heads of the common ancestors of the given revs
39 /// return the heads of the common ancestors of the given revs
43 def commonancestorsheads(&self, *args, **kw) -> PyResult<PyObject> {
40 def commonancestorsheads(&self, *args, **kw) -> PyResult<PyObject> {
44 self.call_cindex(py, "commonancestorsheads", args, kw)
41 self.call_cindex(py, "commonancestorsheads", args, kw)
45 }
42 }
46
43
47 /// clear the index caches
44 /// clear the index caches
48 def clearcaches(&self, *args, **kw) -> PyResult<PyObject> {
45 def clearcaches(&self, *args, **kw) -> PyResult<PyObject> {
49 self.call_cindex(py, "clearcaches", args, kw)
46 self.call_cindex(py, "clearcaches", args, kw)
50 }
47 }
51
48
52 /// get an index entry
49 /// get an index entry
53 def get(&self, *args, **kw) -> PyResult<PyObject> {
50 def get(&self, *args, **kw) -> PyResult<PyObject> {
54 self.call_cindex(py, "get", args, kw)
51 self.call_cindex(py, "get", args, kw)
55 }
52 }
56
53
57 /// return `rev` associated with a node or None
54 /// return `rev` associated with a node or None
58 def get_rev(&self, *args, **kw) -> PyResult<PyObject> {
55 def get_rev(&self, *args, **kw) -> PyResult<PyObject> {
59 self.call_cindex(py, "get_rev", args, kw)
56 self.call_cindex(py, "get_rev", args, kw)
60 }
57 }
61
58
62 /// return True if the node exist in the index
59 /// return True if the node exist in the index
63 def has_node(&self, *args, **kw) -> PyResult<PyObject> {
60 def has_node(&self, *args, **kw) -> PyResult<PyObject> {
64 self.call_cindex(py, "has_node", args, kw)
61 self.call_cindex(py, "has_node", args, kw)
65 }
62 }
66
63
67 /// return `rev` associated with a node or raise RevlogError
64 /// return `rev` associated with a node or raise RevlogError
68 def rev(&self, *args, **kw) -> PyResult<PyObject> {
65 def rev(&self, *args, **kw) -> PyResult<PyObject> {
69 self.call_cindex(py, "rev", args, kw)
66 self.call_cindex(py, "rev", args, kw)
70 }
67 }
71
68
72 /// compute phases
69 /// compute phases
73 def computephasesmapsets(&self, *args, **kw) -> PyResult<PyObject> {
70 def computephasesmapsets(&self, *args, **kw) -> PyResult<PyObject> {
74 self.call_cindex(py, "computephasesmapsets", args, kw)
71 self.call_cindex(py, "computephasesmapsets", args, kw)
75 }
72 }
76
73
77 /// reachableroots
74 /// reachableroots
78 def reachableroots2(&self, *args, **kw) -> PyResult<PyObject> {
75 def reachableroots2(&self, *args, **kw) -> PyResult<PyObject> {
79 self.call_cindex(py, "reachableroots2", args, kw)
76 self.call_cindex(py, "reachableroots2", args, kw)
80 }
77 }
81
78
82 /// get head revisions
79 /// get head revisions
83 def headrevs(&self, *args, **kw) -> PyResult<PyObject> {
80 def headrevs(&self, *args, **kw) -> PyResult<PyObject> {
84 self.call_cindex(py, "headrevs", args, kw)
81 self.call_cindex(py, "headrevs", args, kw)
85 }
82 }
86
83
87 /// get filtered head revisions
84 /// get filtered head revisions
88 def headrevsfiltered(&self, *args, **kw) -> PyResult<PyObject> {
85 def headrevsfiltered(&self, *args, **kw) -> PyResult<PyObject> {
89 self.call_cindex(py, "headrevsfiltered", args, kw)
86 self.call_cindex(py, "headrevsfiltered", args, kw)
90 }
87 }
91
88
92 /// True if the object is a snapshot
89 /// True if the object is a snapshot
93 def issnapshot(&self, *args, **kw) -> PyResult<PyObject> {
90 def issnapshot(&self, *args, **kw) -> PyResult<PyObject> {
94 self.call_cindex(py, "issnapshot", args, kw)
91 self.call_cindex(py, "issnapshot", args, kw)
95 }
92 }
96
93
97 /// Gather snapshot data in a cache dict
94 /// Gather snapshot data in a cache dict
98 def findsnapshots(&self, *args, **kw) -> PyResult<PyObject> {
95 def findsnapshots(&self, *args, **kw) -> PyResult<PyObject> {
99 self.call_cindex(py, "findsnapshots", args, kw)
96 self.call_cindex(py, "findsnapshots", args, kw)
100 }
97 }
101
98
102 /// determine revisions with deltas to reconstruct fulltext
99 /// determine revisions with deltas to reconstruct fulltext
103 def deltachain(&self, *args, **kw) -> PyResult<PyObject> {
100 def deltachain(&self, *args, **kw) -> PyResult<PyObject> {
104 self.call_cindex(py, "deltachain", args, kw)
101 self.call_cindex(py, "deltachain", args, kw)
105 }
102 }
106
103
107 /// slice planned chunk read to reach a density threshold
104 /// slice planned chunk read to reach a density threshold
108 def slicechunktodensity(&self, *args, **kw) -> PyResult<PyObject> {
105 def slicechunktodensity(&self, *args, **kw) -> PyResult<PyObject> {
109 self.call_cindex(py, "slicechunktodensity", args, kw)
106 self.call_cindex(py, "slicechunktodensity", args, kw)
110 }
107 }
111
108
112 /// append an index entry
109 /// append an index entry
113 def append(&self, *args, **kw) -> PyResult<PyObject> {
110 def append(&self, *args, **kw) -> PyResult<PyObject> {
114 self.call_cindex(py, "append", args, kw)
111 self.call_cindex(py, "append", args, kw)
115 }
112 }
116
113
117 /// match a potentially ambiguous node ID
114 /// match a potentially ambiguous node ID
118 def partialmatch(&self, *args, **kw) -> PyResult<PyObject> {
115 def partialmatch(&self, *args, **kw) -> PyResult<PyObject> {
119 self.call_cindex(py, "partialmatch", args, kw)
116 self.call_cindex(py, "partialmatch", args, kw)
120 }
117 }
121
118
122 /// find length of shortest hex nodeid of a binary ID
119 /// find length of shortest hex nodeid of a binary ID
123 def shortest(&self, *args, **kw) -> PyResult<PyObject> {
120 def shortest(&self, *args, **kw) -> PyResult<PyObject> {
124 self.call_cindex(py, "shortest", args, kw)
121 self.call_cindex(py, "shortest", args, kw)
125 }
122 }
126
123
127 /// stats for the index
124 /// stats for the index
128 def stats(&self, *args, **kw) -> PyResult<PyObject> {
125 def stats(&self, *args, **kw) -> PyResult<PyObject> {
129 self.call_cindex(py, "stats", args, kw)
126 self.call_cindex(py, "stats", args, kw)
130 }
127 }
131
128
132 // index_sequence_methods and index_mapping_methods.
129 // index_sequence_methods and index_mapping_methods.
133 //
130 //
134 // Since we call back through the high level Python API,
131 // Since we call back through the high level Python API,
135 // there's no point making a distinction between index_get
132 // there's no point making a distinction between index_get
136 // and index_getitem.
133 // and index_getitem.
137
134
138 def __len__(&self) -> PyResult<usize> {
135 def __len__(&self) -> PyResult<usize> {
139 self.cindex(py).borrow().inner().len(py)
136 self.cindex(py).borrow().inner().len(py)
140 }
137 }
141
138
142 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
139 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
143 // this conversion seems needless, but that's actually because
140 // this conversion seems needless, but that's actually because
144 // `index_getitem` does not handle conversion from PyLong,
141 // `index_getitem` does not handle conversion from PyLong,
145 // which expressions such as [e for e in index] internally use.
142 // which expressions such as [e for e in index] internally use.
146 // Note that we don't seem to have a direct way to call
143 // Note that we don't seem to have a direct way to call
147 // PySequence_GetItem (does the job), which would be better for
144 // PySequence_GetItem (does the job), which would be better for
148 // for performance
145 // for performance
149 let key = match key.extract::<Revision>(py) {
146 let key = match key.extract::<Revision>(py) {
150 Ok(rev) => rev.to_py_object(py).into_object(),
147 Ok(rev) => rev.to_py_object(py).into_object(),
151 Err(_) => key,
148 Err(_) => key,
152 };
149 };
153 self.cindex(py).borrow().inner().get_item(py, key)
150 self.cindex(py).borrow().inner().get_item(py, key)
154 }
151 }
155
152
156 def __setitem__(&self, key: PyObject, value: PyObject) -> PyResult<()> {
153 def __setitem__(&self, key: PyObject, value: PyObject) -> PyResult<()> {
157 self.cindex(py).borrow().inner().set_item(py, key, value)
154 self.cindex(py).borrow().inner().set_item(py, key, value)
158 }
155 }
159
156
160 def __delitem__(&self, key: PyObject) -> PyResult<()> {
157 def __delitem__(&self, key: PyObject) -> PyResult<()> {
161 self.cindex(py).borrow().inner().del_item(py, key)
158 self.cindex(py).borrow().inner().del_item(py, key)
162 }
159 }
163
160
164 def __contains__(&self, item: PyObject) -> PyResult<bool> {
161 def __contains__(&self, item: PyObject) -> PyResult<bool> {
165 // ObjectProtocol does not seem to provide contains(), so
162 // ObjectProtocol does not seem to provide contains(), so
166 // this is an equivalent implementation of the index_contains()
163 // this is an equivalent implementation of the index_contains()
167 // defined in revlog.c
164 // defined in revlog.c
168 let cindex = self.cindex(py).borrow();
165 let cindex = self.cindex(py).borrow();
169 match item.extract::<Revision>(py) {
166 match item.extract::<Revision>(py) {
170 Ok(rev) => {
167 Ok(rev) => {
171 Ok(rev >= -1 && rev < cindex.inner().len(py)? as Revision)
168 Ok(rev >= -1 && rev < cindex.inner().len(py)? as Revision)
172 }
169 }
173 Err(_) => {
170 Err(_) => {
174 cindex.inner().call_method(
171 cindex.inner().call_method(
175 py,
172 py,
176 "has_node",
173 "has_node",
177 PyTuple::new(py, &[item]),
174 PyTuple::new(py, &[item]),
178 None)?
175 None)?
179 .extract(py)
176 .extract(py)
180 }
177 }
181 }
178 }
182 }
179 }
183
180
184
181
185 });
182 });
186
183
187 impl MixedIndex {
184 impl MixedIndex {
188 /// forward a method call to the underlying C index
185 /// forward a method call to the underlying C index
189 fn call_cindex(
186 fn call_cindex(
190 &self,
187 &self,
191 py: Python,
188 py: Python,
192 name: &str,
189 name: &str,
193 args: &PyTuple,
190 args: &PyTuple,
194 kwargs: Option<&PyDict>,
191 kwargs: Option<&PyDict>,
195 ) -> PyResult<PyObject> {
192 ) -> PyResult<PyObject> {
196 self.cindex(py)
193 self.cindex(py)
197 .borrow()
194 .borrow()
198 .inner()
195 .inner()
199 .call_method(py, name, args, kwargs)
196 .call_method(py, name, args, kwargs)
200 }
197 }
198
199 pub fn clone_cindex(&self, py: Python) -> cindex::Index {
200 self.cindex(py).borrow().clone_ref(py)
201 }
201 }
202 }
202
203
203 /// Create the module, with __package__ given from parent
204 /// Create the module, with __package__ given from parent
204 pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
205 pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
205 let dotted_name = &format!("{}.revlog", package);
206 let dotted_name = &format!("{}.revlog", package);
206 let m = PyModule::new(py, dotted_name)?;
207 let m = PyModule::new(py, dotted_name)?;
207 m.add(py, "__package__", package)?;
208 m.add(py, "__package__", package)?;
208 m.add(py, "__doc__", "RevLog - Rust implementations")?;
209 m.add(py, "__doc__", "RevLog - Rust implementations")?;
209
210
210 m.add_class::<MixedIndex>(py)?;
211 m.add_class::<MixedIndex>(py)?;
211
212
212 let sys = PyModule::import(py, "sys")?;
213 let sys = PyModule::import(py, "sys")?;
213 let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
214 let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
214 sys_modules.set_item(py, dotted_name, &m)?;
215 sys_modules.set_item(py, dotted_name, &m)?;
215
216
216 Ok(m)
217 Ok(m)
217 }
218 }
General Comments 0
You need to be logged in to leave comments. Login now