##// END OF EJS Templates
rust: iterator bindings to C code...
Georges Racinet -
r40308:a36c5e23 default
parent child Browse files
Show More
@@ -0,0 +1,12
1 [package]
2 name = "hgdirectffi"
3 version = "0.1.0"
4 authors = ["Georges Racinet <gracinet@anybox.fr>"]
5 description = "Low level Python bindings for hg-core, going through existing C extensions"
6
7 [dependencies]
8 libc = "*"
9 hg-core = { path = "../hg-core" }
10
11 [lib]
12 crate-type = ["staticlib"]
@@ -0,0 +1,229
1 // Copyright 2018 Georges Racinet <gracinet@anybox.fr>
2 //
3 // This software may be used and distributed according to the terms of the
4 // GNU General Public License version 2 or any later version.
5
6 //! Bindings for CPython extension code
7 //!
8 //! This exposes methods to build and use a `rustlazyancestors` iterator
9 //! from C code, using an index and its parents function that are passed
10 //! from the caller at instantiation.
11
12 use hg::AncestorsIterator;
13 use hg::{Graph, GraphError, Revision, NULL_REVISION};
14 use libc::{c_int, c_long, c_void, ssize_t};
15 use std::ptr::null_mut;
16 use std::slice;
17
18 type IndexPtr = *mut c_void;
19 type IndexParentsFn =
20 unsafe extern "C" fn(index: IndexPtr, rev: ssize_t, ps: *mut [c_int; 2], max_rev: c_int)
21 -> c_int;
22
23 /// A Graph backed up by objects and functions from revlog.c
24 ///
25 /// This implementation of the Graph trait, relies on (pointers to)
26 /// - the C index object (`index` member)
27 /// - the `index_get_parents()` function (`parents` member)
28 pub struct Index {
29 index: IndexPtr,
30 parents: IndexParentsFn,
31 }
32
33 impl Index {
34 pub fn new(index: IndexPtr, parents: IndexParentsFn) -> Self {
35 Index {
36 index: index,
37 parents: parents,
38 }
39 }
40 }
41
42 impl Graph for Index {
43 /// wrap a call to the C extern parents function
44 fn parents(&self, rev: Revision) -> Result<(Revision, Revision), GraphError> {
45 let mut res: [c_int; 2] = [0; 2];
46 let code =
47 unsafe { (self.parents)(self.index, rev as ssize_t, &mut res as *mut [c_int; 2], rev) };
48 match code {
49 0 => Ok((res[0], res[1])),
50 _ => Err(GraphError::ParentOutOfRange(rev)),
51 }
52 }
53 }
54
55 /// Wrapping of AncestorsIterator<Index> constructor, for C callers.
56 ///
57 /// Besides `initrevs`, `stoprev` and `inclusive`, that are converted
58 /// we receive the index and the parents function as pointers
59 #[no_mangle]
60 pub extern "C" fn rustlazyancestors_init(
61 index: IndexPtr,
62 parents: IndexParentsFn,
63 initrevslen: usize,
64 initrevs: *mut c_long,
65 stoprev: c_long,
66 inclusive: c_int,
67 ) -> *mut AncestorsIterator<Index> {
68 unsafe {
69 raw_init(
70 Index::new(index, parents),
71 initrevslen,
72 initrevs,
73 stoprev,
74 inclusive,
75 )
76 }
77 }
78
79 /// Testable (for any Graph) version of rustlazyancestors_init
80 #[inline]
81 unsafe fn raw_init<G: Graph>(
82 graph: G,
83 initrevslen: usize,
84 initrevs: *mut c_long,
85 stoprev: c_long,
86 inclusive: c_int,
87 ) -> *mut AncestorsIterator<G> {
88 let inclb = match inclusive {
89 0 => false,
90 1 => true,
91 _ => {
92 return null_mut();
93 }
94 };
95
96 let slice = slice::from_raw_parts(initrevs, initrevslen);
97
98 Box::into_raw(Box::new(match AncestorsIterator::new(
99 graph,
100 slice.into_iter().map(|&r| r as Revision),
101 stoprev as Revision,
102 inclb,
103 ) {
104 Ok(it) => it,
105 Err(_) => {
106 return null_mut();
107 }
108 }))
109 }
110
111 /// Deallocator to be called from C code
112 #[no_mangle]
113 pub extern "C" fn rustlazyancestors_drop(raw_iter: *mut AncestorsIterator<Index>) {
114 raw_drop(raw_iter);
115 }
116
117 /// Testable (for any Graph) version of rustlazayancestors_drop
118 #[inline]
119 fn raw_drop<G: Graph>(raw_iter: *mut AncestorsIterator<G>) {
120 unsafe {
121 Box::from_raw(raw_iter);
122 }
123 }
124
125 /// Iteration main method to be called from C code
126 ///
127 /// We convert the end of iteration into NULL_REVISION,
128 /// it will be up to the C wrapper to convert that back into a Python end of
129 /// iteration
130 #[no_mangle]
131 pub extern "C" fn rustlazyancestors_next(raw: *mut AncestorsIterator<Index>) -> c_long {
132 raw_next(raw)
133 }
134
135 /// Testable (for any Graph) version of rustlazayancestors_next
136 #[inline]
137 fn raw_next<G: Graph>(raw: *mut AncestorsIterator<G>) -> c_long {
138 let as_ref = unsafe { &mut *raw };
139 as_ref.next().unwrap_or(NULL_REVISION) as c_long
140 }
141
142 #[cfg(test)]
143 mod tests {
144 use super::*;
145 use std::thread;
146
147 #[derive(Clone, Debug)]
148 struct Stub;
149
150 impl Graph for Stub {
151 fn parents(&self, r: Revision) -> Result<(Revision, Revision), GraphError> {
152 match r {
153 25 => Err(GraphError::ParentOutOfRange(25)),
154 _ => Ok((1, 2)),
155 }
156 }
157 }
158
159 /// Helper for test_init_next()
160 fn stub_raw_init(
161 initrevslen: usize,
162 initrevs: usize,
163 stoprev: c_long,
164 inclusive: c_int,
165 ) -> usize {
166 unsafe {
167 raw_init(
168 Stub,
169 initrevslen,
170 initrevs as *mut c_long,
171 stoprev,
172 inclusive,
173 ) as usize
174 }
175 }
176
177 fn stub_raw_init_from_vec(
178 mut initrevs: Vec<c_long>,
179 stoprev: c_long,
180 inclusive: c_int,
181 ) -> *mut AncestorsIterator<Stub> {
182 unsafe {
183 raw_init(
184 Stub,
185 initrevs.len(),
186 initrevs.as_mut_ptr(),
187 stoprev,
188 inclusive,
189 )
190 }
191 }
192
193 #[test]
194 // Test what happens when we init an Iterator as with the exposed C ABI
195 // and try to use it afterwards
196 // We spawn new threads, in order to make memory consistency harder
197 // but this forces us to convert the pointers into shareable usizes.
198 fn test_init_next() {
199 let mut initrevs: Vec<c_long> = vec![11, 13];
200 let initrevs_len = initrevs.len();
201 let initrevs_ptr = initrevs.as_mut_ptr() as usize;
202 let handler = thread::spawn(move || stub_raw_init(initrevs_len, initrevs_ptr, 0, 1));
203 let raw = handler.join().unwrap() as *mut AncestorsIterator<Stub>;
204
205 assert_eq!(raw_next(raw), 13);
206 assert_eq!(raw_next(raw), 11);
207 assert_eq!(raw_next(raw), 2);
208 assert_eq!(raw_next(raw), 1);
209 assert_eq!(raw_next(raw), NULL_REVISION as c_long);
210 raw_drop(raw);
211 }
212
213 #[test]
214 fn test_init_wrong_bool() {
215 assert_eq!(stub_raw_init_from_vec(vec![11, 13], 0, 2), null_mut());
216 }
217
218 #[test]
219 fn test_empty() {
220 let raw = stub_raw_init_from_vec(vec![], 0, 1);
221 assert_eq!(raw_next(raw), NULL_REVISION as c_long);
222 raw_drop(raw);
223 }
224
225 #[test]
226 fn test_init_err_out_of_range() {
227 assert!(stub_raw_init_from_vec(vec![25], 0, 0).is_null());
228 }
229 }
@@ -0,0 +1,16
1 // Copyright 2018 Georges Racinet <gracinet@anybox.fr>
2 //
3 // This software may be used and distributed according to the terms of the
4 // GNU General Public License version 2 or any later version.
5
6 //! Bindings for CPython extension code
7 //!
8 //! This exposes methods to build and use a `rustlazyancestors` iterator
9 //! from C code, using an index and its parents function that are passed
10 //! from the caller at instantiation.
11
12 extern crate hg;
13 extern crate libc;
14
15 mod ancestors;
16 pub use ancestors::{rustlazyancestors_drop, rustlazyancestors_init, rustlazyancestors_next};
@@ -1,131 +1,139
1 [[package]]
1 [[package]]
2 name = "aho-corasick"
2 name = "aho-corasick"
3 version = "0.5.3"
3 version = "0.5.3"
4 source = "registry+https://github.com/rust-lang/crates.io-index"
4 source = "registry+https://github.com/rust-lang/crates.io-index"
5 dependencies = [
5 dependencies = [
6 "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
6 "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
7 ]
7 ]
8
8
9 [[package]]
9 [[package]]
10 name = "cpython"
10 name = "cpython"
11 version = "0.1.0"
11 version = "0.1.0"
12 source = "git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52#c90d65cf84abfffce7ef54476bbfed56017a2f52"
12 source = "git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52#c90d65cf84abfffce7ef54476bbfed56017a2f52"
13 dependencies = [
13 dependencies = [
14 "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)",
14 "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)",
15 "num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
15 "num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
16 "python27-sys 0.1.2 (git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52)",
16 "python27-sys 0.1.2 (git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52)",
17 ]
17 ]
18
18
19 [[package]]
19 [[package]]
20 name = "hg-core"
20 name = "hg-core"
21 version = "0.1.0"
21 version = "0.1.0"
22
22
23 [[package]]
23 [[package]]
24 name = "hgcli"
24 name = "hgcli"
25 version = "0.1.0"
25 version = "0.1.0"
26 dependencies = [
26 dependencies = [
27 "cpython 0.1.0 (git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52)",
27 "cpython 0.1.0 (git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52)",
28 "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)",
28 "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)",
29 "python27-sys 0.1.2 (git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52)",
29 "python27-sys 0.1.2 (git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52)",
30 ]
30 ]
31
31
32 [[package]]
32 [[package]]
33 name = "hgdirectffi"
34 version = "0.1.0"
35 dependencies = [
36 "hg-core 0.1.0",
37 "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)",
38 ]
39
40 [[package]]
33 name = "kernel32-sys"
41 name = "kernel32-sys"
34 version = "0.2.2"
42 version = "0.2.2"
35 source = "registry+https://github.com/rust-lang/crates.io-index"
43 source = "registry+https://github.com/rust-lang/crates.io-index"
36 dependencies = [
44 dependencies = [
37 "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
45 "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
38 "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
46 "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
39 ]
47 ]
40
48
41 [[package]]
49 [[package]]
42 name = "libc"
50 name = "libc"
43 version = "0.2.35"
51 version = "0.2.35"
44 source = "registry+https://github.com/rust-lang/crates.io-index"
52 source = "registry+https://github.com/rust-lang/crates.io-index"
45
53
46 [[package]]
54 [[package]]
47 name = "memchr"
55 name = "memchr"
48 version = "0.1.11"
56 version = "0.1.11"
49 source = "registry+https://github.com/rust-lang/crates.io-index"
57 source = "registry+https://github.com/rust-lang/crates.io-index"
50 dependencies = [
58 dependencies = [
51 "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)",
59 "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)",
52 ]
60 ]
53
61
54 [[package]]
62 [[package]]
55 name = "num-traits"
63 name = "num-traits"
56 version = "0.1.41"
64 version = "0.1.41"
57 source = "registry+https://github.com/rust-lang/crates.io-index"
65 source = "registry+https://github.com/rust-lang/crates.io-index"
58
66
59 [[package]]
67 [[package]]
60 name = "python27-sys"
68 name = "python27-sys"
61 version = "0.1.2"
69 version = "0.1.2"
62 source = "git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52#c90d65cf84abfffce7ef54476bbfed56017a2f52"
70 source = "git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52#c90d65cf84abfffce7ef54476bbfed56017a2f52"
63 dependencies = [
71 dependencies = [
64 "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)",
72 "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)",
65 "regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)",
73 "regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)",
66 ]
74 ]
67
75
68 [[package]]
76 [[package]]
69 name = "regex"
77 name = "regex"
70 version = "0.1.80"
78 version = "0.1.80"
71 source = "registry+https://github.com/rust-lang/crates.io-index"
79 source = "registry+https://github.com/rust-lang/crates.io-index"
72 dependencies = [
80 dependencies = [
73 "aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
81 "aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
74 "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
82 "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
75 "regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
83 "regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
76 "thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
84 "thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
77 "utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
85 "utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
78 ]
86 ]
79
87
80 [[package]]
88 [[package]]
81 name = "regex-syntax"
89 name = "regex-syntax"
82 version = "0.3.9"
90 version = "0.3.9"
83 source = "registry+https://github.com/rust-lang/crates.io-index"
91 source = "registry+https://github.com/rust-lang/crates.io-index"
84
92
85 [[package]]
93 [[package]]
86 name = "thread-id"
94 name = "thread-id"
87 version = "2.0.0"
95 version = "2.0.0"
88 source = "registry+https://github.com/rust-lang/crates.io-index"
96 source = "registry+https://github.com/rust-lang/crates.io-index"
89 dependencies = [
97 dependencies = [
90 "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
98 "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
91 "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)",
99 "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)",
92 ]
100 ]
93
101
94 [[package]]
102 [[package]]
95 name = "thread_local"
103 name = "thread_local"
96 version = "0.2.7"
104 version = "0.2.7"
97 source = "registry+https://github.com/rust-lang/crates.io-index"
105 source = "registry+https://github.com/rust-lang/crates.io-index"
98 dependencies = [
106 dependencies = [
99 "thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
107 "thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
100 ]
108 ]
101
109
102 [[package]]
110 [[package]]
103 name = "utf8-ranges"
111 name = "utf8-ranges"
104 version = "0.1.3"
112 version = "0.1.3"
105 source = "registry+https://github.com/rust-lang/crates.io-index"
113 source = "registry+https://github.com/rust-lang/crates.io-index"
106
114
107 [[package]]
115 [[package]]
108 name = "winapi"
116 name = "winapi"
109 version = "0.2.8"
117 version = "0.2.8"
110 source = "registry+https://github.com/rust-lang/crates.io-index"
118 source = "registry+https://github.com/rust-lang/crates.io-index"
111
119
112 [[package]]
120 [[package]]
113 name = "winapi-build"
121 name = "winapi-build"
114 version = "0.1.1"
122 version = "0.1.1"
115 source = "registry+https://github.com/rust-lang/crates.io-index"
123 source = "registry+https://github.com/rust-lang/crates.io-index"
116
124
117 [metadata]
125 [metadata]
118 "checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66"
126 "checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66"
119 "checksum cpython 0.1.0 (git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52)" = "<none>"
127 "checksum cpython 0.1.0 (git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52)" = "<none>"
120 "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
128 "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
121 "checksum libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)" = "96264e9b293e95d25bfcbbf8a88ffd1aedc85b754eba8b7d78012f638ba220eb"
129 "checksum libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)" = "96264e9b293e95d25bfcbbf8a88ffd1aedc85b754eba8b7d78012f638ba220eb"
122 "checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20"
130 "checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20"
123 "checksum num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "cacfcab5eb48250ee7d0c7896b51a2c5eec99c1feea5f32025635f5ae4b00070"
131 "checksum num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "cacfcab5eb48250ee7d0c7896b51a2c5eec99c1feea5f32025635f5ae4b00070"
124 "checksum python27-sys 0.1.2 (git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52)" = "<none>"
132 "checksum python27-sys 0.1.2 (git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52)" = "<none>"
125 "checksum regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f"
133 "checksum regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f"
126 "checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957"
134 "checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957"
127 "checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03"
135 "checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03"
128 "checksum thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5"
136 "checksum thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5"
129 "checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f"
137 "checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f"
130 "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
138 "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
131 "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
139 "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
@@ -1,3 +1,3
1 [workspace]
1 [workspace]
2 members = ["hgcli", "hg-core"]
2 members = ["hgcli", "hg-core", "hg-direct-ffi"]
3 exclude = ["chg"]
3 exclude = ["chg"]
General Comments 0
You need to be logged in to leave comments. Login now