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