Show More
@@ -1,196 +1,209 b'' | |||||
1 | // discovery.rs |
|
1 | // discovery.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 | //! Discovery operations |
|
8 | //! Discovery operations | |
9 | //! |
|
9 | //! | |
10 | //! This is a Rust counterpart to the `partialdiscovery` class of |
|
10 | //! This is a Rust counterpart to the `partialdiscovery` class of | |
11 | //! `mercurial.setdiscovery` |
|
11 | //! `mercurial.setdiscovery` | |
12 |
|
12 | |||
13 | use super::{Graph, GraphError, Revision}; |
|
13 | use super::{Graph, GraphError, Revision}; | |
14 | use crate::ancestors::MissingAncestors; |
|
14 | use crate::ancestors::MissingAncestors; | |
15 | use crate::dagops; |
|
15 | use crate::dagops; | |
16 | use std::collections::HashSet; |
|
16 | use std::collections::HashSet; | |
17 |
|
17 | |||
18 | pub struct PartialDiscovery<G: Graph + Clone> { |
|
18 | pub struct PartialDiscovery<G: Graph + Clone> { | |
19 | target_heads: Option<Vec<Revision>>, |
|
19 | target_heads: Option<Vec<Revision>>, | |
20 | graph: G, // plays the role of self._repo |
|
20 | graph: G, // plays the role of self._repo | |
21 | common: MissingAncestors<G>, |
|
21 | common: MissingAncestors<G>, | |
22 | undecided: Option<HashSet<Revision>>, |
|
22 | undecided: Option<HashSet<Revision>>, | |
23 | missing: HashSet<Revision>, |
|
23 | missing: HashSet<Revision>, | |
24 | } |
|
24 | } | |
25 |
|
25 | |||
|
26 | pub struct DiscoveryStats { | |||
|
27 | pub undecided: Option<usize>, | |||
|
28 | } | |||
|
29 | ||||
26 | impl<G: Graph + Clone> PartialDiscovery<G> { |
|
30 | impl<G: Graph + Clone> PartialDiscovery<G> { | |
27 | /// Create a PartialDiscovery object, with the intent |
|
31 | /// Create a PartialDiscovery object, with the intent | |
28 | /// of comparing our `::<target_heads>` revset to the contents of another |
|
32 | /// of comparing our `::<target_heads>` revset to the contents of another | |
29 | /// repo. |
|
33 | /// repo. | |
30 | /// |
|
34 | /// | |
31 | /// For now `target_heads` is passed as a vector, and will be used |
|
35 | /// For now `target_heads` is passed as a vector, and will be used | |
32 | /// at the first call to `ensure_undecided()`. |
|
36 | /// at the first call to `ensure_undecided()`. | |
33 | /// |
|
37 | /// | |
34 | /// If we want to make the signature more flexible, |
|
38 | /// If we want to make the signature more flexible, | |
35 | /// we'll have to make it a type argument of `PartialDiscovery` or a trait |
|
39 | /// we'll have to make it a type argument of `PartialDiscovery` or a trait | |
36 | /// object since we'll keep it in the meanwhile |
|
40 | /// object since we'll keep it in the meanwhile | |
37 | pub fn new(graph: G, target_heads: Vec<Revision>) -> Self { |
|
41 | pub fn new(graph: G, target_heads: Vec<Revision>) -> Self { | |
38 | PartialDiscovery { |
|
42 | PartialDiscovery { | |
39 | undecided: None, |
|
43 | undecided: None, | |
40 | target_heads: Some(target_heads), |
|
44 | target_heads: Some(target_heads), | |
41 | graph: graph.clone(), |
|
45 | graph: graph.clone(), | |
42 | common: MissingAncestors::new(graph, vec![]), |
|
46 | common: MissingAncestors::new(graph, vec![]), | |
43 | missing: HashSet::new(), |
|
47 | missing: HashSet::new(), | |
44 | } |
|
48 | } | |
45 | } |
|
49 | } | |
46 |
|
50 | |||
47 | /// Register revisions known as being common |
|
51 | /// Register revisions known as being common | |
48 | pub fn add_common_revisions( |
|
52 | pub fn add_common_revisions( | |
49 | &mut self, |
|
53 | &mut self, | |
50 | common: impl IntoIterator<Item = Revision>, |
|
54 | common: impl IntoIterator<Item = Revision>, | |
51 | ) -> Result<(), GraphError> { |
|
55 | ) -> Result<(), GraphError> { | |
52 | self.common.add_bases(common); |
|
56 | self.common.add_bases(common); | |
53 | if let Some(ref mut undecided) = self.undecided { |
|
57 | if let Some(ref mut undecided) = self.undecided { | |
54 | self.common.remove_ancestors_from(undecided)?; |
|
58 | self.common.remove_ancestors_from(undecided)?; | |
55 | } |
|
59 | } | |
56 | Ok(()) |
|
60 | Ok(()) | |
57 | } |
|
61 | } | |
58 |
|
62 | |||
59 | /// Register revisions known as being missing |
|
63 | /// Register revisions known as being missing | |
60 | pub fn add_missing_revisions( |
|
64 | pub fn add_missing_revisions( | |
61 | &mut self, |
|
65 | &mut self, | |
62 | missing: impl IntoIterator<Item = Revision>, |
|
66 | missing: impl IntoIterator<Item = Revision>, | |
63 | ) -> Result<(), GraphError> { |
|
67 | ) -> Result<(), GraphError> { | |
64 | self.ensure_undecided()?; |
|
68 | self.ensure_undecided()?; | |
65 | let range = dagops::range( |
|
69 | let range = dagops::range( | |
66 | &self.graph, |
|
70 | &self.graph, | |
67 | missing, |
|
71 | missing, | |
68 | self.undecided.as_ref().unwrap().iter().cloned(), |
|
72 | self.undecided.as_ref().unwrap().iter().cloned(), | |
69 | )?; |
|
73 | )?; | |
70 | let undecided_mut = self.undecided.as_mut().unwrap(); |
|
74 | let undecided_mut = self.undecided.as_mut().unwrap(); | |
71 | for missrev in range { |
|
75 | for missrev in range { | |
72 | self.missing.insert(missrev); |
|
76 | self.missing.insert(missrev); | |
73 | undecided_mut.remove(&missrev); |
|
77 | undecided_mut.remove(&missrev); | |
74 | } |
|
78 | } | |
75 | Ok(()) |
|
79 | Ok(()) | |
76 | } |
|
80 | } | |
77 |
|
81 | |||
78 | /// Do we have any information about the peer? |
|
82 | /// Do we have any information about the peer? | |
79 | pub fn has_info(&self) -> bool { |
|
83 | pub fn has_info(&self) -> bool { | |
80 | self.common.has_bases() |
|
84 | self.common.has_bases() | |
81 | } |
|
85 | } | |
82 |
|
86 | |||
83 | /// Did we acquire full knowledge of our Revisions that the peer has? |
|
87 | /// Did we acquire full knowledge of our Revisions that the peer has? | |
84 | pub fn is_complete(&self) -> bool { |
|
88 | pub fn is_complete(&self) -> bool { | |
85 | self.undecided.as_ref().map_or(false, |s| s.is_empty()) |
|
89 | self.undecided.as_ref().map_or(false, |s| s.is_empty()) | |
86 | } |
|
90 | } | |
87 |
|
91 | |||
88 | /// Return the heads of the currently known common set of revisions. |
|
92 | /// Return the heads of the currently known common set of revisions. | |
89 | /// |
|
93 | /// | |
90 | /// If the discovery process is not complete (see `is_complete()`), the |
|
94 | /// If the discovery process is not complete (see `is_complete()`), the | |
91 | /// caller must be aware that this is an intermediate state. |
|
95 | /// caller must be aware that this is an intermediate state. | |
92 | /// |
|
96 | /// | |
93 | /// On the other hand, if it is complete, then this is currently |
|
97 | /// On the other hand, if it is complete, then this is currently | |
94 | /// the only way to retrieve the end results of the discovery process. |
|
98 | /// the only way to retrieve the end results of the discovery process. | |
95 | /// |
|
99 | /// | |
96 | /// We may introduce in the future an `into_common_heads` call that |
|
100 | /// We may introduce in the future an `into_common_heads` call that | |
97 | /// would be more appropriate for normal Rust callers, dropping `self` |
|
101 | /// would be more appropriate for normal Rust callers, dropping `self` | |
98 | /// if it is complete. |
|
102 | /// if it is complete. | |
99 | pub fn common_heads(&self) -> Result<HashSet<Revision>, GraphError> { |
|
103 | pub fn common_heads(&self) -> Result<HashSet<Revision>, GraphError> { | |
100 | self.common.bases_heads() |
|
104 | self.common.bases_heads() | |
101 | } |
|
105 | } | |
102 |
|
106 | |||
103 | /// Force first computation of `self.undecided` |
|
107 | /// Force first computation of `self.undecided` | |
104 | /// |
|
108 | /// | |
105 | /// After this, `self.undecided.as_ref()` and `.as_mut()` can be |
|
109 | /// After this, `self.undecided.as_ref()` and `.as_mut()` can be | |
106 | /// unwrapped to get workable immutable or mutable references without |
|
110 | /// unwrapped to get workable immutable or mutable references without | |
107 | /// any panic. |
|
111 | /// any panic. | |
108 | /// |
|
112 | /// | |
109 | /// This is an imperative call instead of an access with added lazyness |
|
113 | /// This is an imperative call instead of an access with added lazyness | |
110 | /// to reduce easily the scope of mutable borrow for the caller, |
|
114 | /// to reduce easily the scope of mutable borrow for the caller, | |
111 | /// compared to undecided(&'a mut self) -> &'a⦠that would keep it |
|
115 | /// compared to undecided(&'a mut self) -> &'a⦠that would keep it | |
112 | /// as long as the resulting immutable one. |
|
116 | /// as long as the resulting immutable one. | |
113 | fn ensure_undecided(&mut self) -> Result<(), GraphError> { |
|
117 | fn ensure_undecided(&mut self) -> Result<(), GraphError> { | |
114 | if self.undecided.is_some() { |
|
118 | if self.undecided.is_some() { | |
115 | return Ok(()); |
|
119 | return Ok(()); | |
116 | } |
|
120 | } | |
117 | let tgt = self.target_heads.take().unwrap(); |
|
121 | let tgt = self.target_heads.take().unwrap(); | |
118 | self.undecided = |
|
122 | self.undecided = | |
119 | Some(self.common.missing_ancestors(tgt)?.into_iter().collect()); |
|
123 | Some(self.common.missing_ancestors(tgt)?.into_iter().collect()); | |
120 | Ok(()) |
|
124 | Ok(()) | |
121 | } |
|
125 | } | |
|
126 | ||||
|
127 | /// Provide statistics about the current state of the discovery process | |||
|
128 | pub fn stats(&self) -> DiscoveryStats { | |||
|
129 | DiscoveryStats { | |||
|
130 | undecided: self.undecided.as_ref().map(|s| s.len()), | |||
|
131 | } | |||
|
132 | } | |||
122 | } |
|
133 | } | |
123 |
|
134 | |||
124 | #[cfg(test)] |
|
135 | #[cfg(test)] | |
125 | mod tests { |
|
136 | mod tests { | |
126 | use super::*; |
|
137 | use super::*; | |
127 | use crate::testing::SampleGraph; |
|
138 | use crate::testing::SampleGraph; | |
128 |
|
139 | |||
129 | /// A PartialDiscovery as for pushing all the heads of `SampleGraph` |
|
140 | /// A PartialDiscovery as for pushing all the heads of `SampleGraph` | |
130 | fn full_disco() -> PartialDiscovery<SampleGraph> { |
|
141 | fn full_disco() -> PartialDiscovery<SampleGraph> { | |
131 | PartialDiscovery::new(SampleGraph, vec![10, 11, 12, 13]) |
|
142 | PartialDiscovery::new(SampleGraph, vec![10, 11, 12, 13]) | |
132 | } |
|
143 | } | |
133 |
|
144 | |||
134 | fn sorted_undecided( |
|
145 | fn sorted_undecided( | |
135 | disco: &PartialDiscovery<SampleGraph>, |
|
146 | disco: &PartialDiscovery<SampleGraph>, | |
136 | ) -> Vec<Revision> { |
|
147 | ) -> Vec<Revision> { | |
137 | let mut as_vec: Vec<Revision> = |
|
148 | let mut as_vec: Vec<Revision> = | |
138 | disco.undecided.as_ref().unwrap().iter().cloned().collect(); |
|
149 | disco.undecided.as_ref().unwrap().iter().cloned().collect(); | |
139 | as_vec.sort(); |
|
150 | as_vec.sort(); | |
140 | as_vec |
|
151 | as_vec | |
141 | } |
|
152 | } | |
142 |
|
153 | |||
143 | fn sorted_missing(disco: &PartialDiscovery<SampleGraph>) -> Vec<Revision> { |
|
154 | fn sorted_missing(disco: &PartialDiscovery<SampleGraph>) -> Vec<Revision> { | |
144 | let mut as_vec: Vec<Revision> = |
|
155 | let mut as_vec: Vec<Revision> = | |
145 | disco.missing.iter().cloned().collect(); |
|
156 | disco.missing.iter().cloned().collect(); | |
146 | as_vec.sort(); |
|
157 | as_vec.sort(); | |
147 | as_vec |
|
158 | as_vec | |
148 | } |
|
159 | } | |
149 |
|
160 | |||
150 | fn sorted_common_heads( |
|
161 | fn sorted_common_heads( | |
151 | disco: &PartialDiscovery<SampleGraph>, |
|
162 | disco: &PartialDiscovery<SampleGraph>, | |
152 | ) -> Result<Vec<Revision>, GraphError> { |
|
163 | ) -> Result<Vec<Revision>, GraphError> { | |
153 | let mut as_vec: Vec<Revision> = |
|
164 | let mut as_vec: Vec<Revision> = | |
154 | disco.common_heads()?.iter().cloned().collect(); |
|
165 | disco.common_heads()?.iter().cloned().collect(); | |
155 | as_vec.sort(); |
|
166 | as_vec.sort(); | |
156 | Ok(as_vec) |
|
167 | Ok(as_vec) | |
157 | } |
|
168 | } | |
158 |
|
169 | |||
159 | #[test] |
|
170 | #[test] | |
160 | fn test_add_common_get_undecided() -> Result<(), GraphError> { |
|
171 | fn test_add_common_get_undecided() -> Result<(), GraphError> { | |
161 | let mut disco = full_disco(); |
|
172 | let mut disco = full_disco(); | |
162 | assert_eq!(disco.undecided, None); |
|
173 | assert_eq!(disco.undecided, None); | |
163 | assert!(!disco.has_info()); |
|
174 | assert!(!disco.has_info()); | |
|
175 | assert_eq!(disco.stats().undecided, None); | |||
164 |
|
176 | |||
165 | disco.add_common_revisions(vec![11, 12])?; |
|
177 | disco.add_common_revisions(vec![11, 12])?; | |
166 | assert!(disco.has_info()); |
|
178 | assert!(disco.has_info()); | |
167 | assert!(!disco.is_complete()); |
|
179 | assert!(!disco.is_complete()); | |
168 | assert!(disco.missing.is_empty()); |
|
180 | assert!(disco.missing.is_empty()); | |
169 |
|
181 | |||
170 | // add_common_revisions did not trigger a premature computation |
|
182 | // add_common_revisions did not trigger a premature computation | |
171 | // of `undecided`, let's check that and ask for them |
|
183 | // of `undecided`, let's check that and ask for them | |
172 | assert_eq!(disco.undecided, None); |
|
184 | assert_eq!(disco.undecided, None); | |
173 | disco.ensure_undecided()?; |
|
185 | disco.ensure_undecided()?; | |
174 | assert_eq!(sorted_undecided(&disco), vec![5, 8, 10, 13]); |
|
186 | assert_eq!(sorted_undecided(&disco), vec![5, 8, 10, 13]); | |
|
187 | assert_eq!(disco.stats().undecided, Some(4)); | |||
175 | Ok(()) |
|
188 | Ok(()) | |
176 | } |
|
189 | } | |
177 |
|
190 | |||
178 | /// in this test, we pretend that our peer misses exactly (8+10):: |
|
191 | /// in this test, we pretend that our peer misses exactly (8+10):: | |
179 | /// and we're comparing all our repo to it (as in a bare push) |
|
192 | /// and we're comparing all our repo to it (as in a bare push) | |
180 | #[test] |
|
193 | #[test] | |
181 | fn test_discovery() -> Result<(), GraphError> { |
|
194 | fn test_discovery() -> Result<(), GraphError> { | |
182 | let mut disco = full_disco(); |
|
195 | let mut disco = full_disco(); | |
183 | disco.add_common_revisions(vec![11, 12])?; |
|
196 | disco.add_common_revisions(vec![11, 12])?; | |
184 | disco.add_missing_revisions(vec![8, 10])?; |
|
197 | disco.add_missing_revisions(vec![8, 10])?; | |
185 | assert_eq!(sorted_undecided(&disco), vec![5]); |
|
198 | assert_eq!(sorted_undecided(&disco), vec![5]); | |
186 | assert_eq!(sorted_missing(&disco), vec![8, 10, 13]); |
|
199 | assert_eq!(sorted_missing(&disco), vec![8, 10, 13]); | |
187 | assert!(!disco.is_complete()); |
|
200 | assert!(!disco.is_complete()); | |
188 |
|
201 | |||
189 | disco.add_common_revisions(vec![5])?; |
|
202 | disco.add_common_revisions(vec![5])?; | |
190 | assert_eq!(sorted_undecided(&disco), vec![]); |
|
203 | assert_eq!(sorted_undecided(&disco), vec![]); | |
191 | assert_eq!(sorted_missing(&disco), vec![8, 10, 13]); |
|
204 | assert_eq!(sorted_missing(&disco), vec![8, 10, 13]); | |
192 | assert!(disco.is_complete()); |
|
205 | assert!(disco.is_complete()); | |
193 | assert_eq!(sorted_common_heads(&disco)?, vec![5, 11, 12]); |
|
206 | assert_eq!(sorted_common_heads(&disco)?, vec![5, 11, 12]); | |
194 | Ok(()) |
|
207 | Ok(()) | |
195 | } |
|
208 | } | |
196 | } |
|
209 | } |
@@ -1,114 +1,125 b'' | |||||
1 | // discovery.rs |
|
1 | // discovery.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 for the `hg::discovery` module provided by the |
|
8 | //! Bindings for the `hg::discovery` module provided by the | |
9 | //! `hg-core` crate. From Python, this will be seen as `rustext.discovery` |
|
9 | //! `hg-core` crate. From Python, this will be seen as `rustext.discovery` | |
10 | //! |
|
10 | //! | |
11 | //! # Classes visible from Python: |
|
11 | //! # Classes visible from Python: | |
12 | //! - [`PartialDiscover`] is the Rust implementation of |
|
12 | //! - [`PartialDiscover`] is the Rust implementation of | |
13 | //! `mercurial.setdiscovery.partialdiscovery`. |
|
13 | //! `mercurial.setdiscovery.partialdiscovery`. | |
14 |
|
14 | |||
15 | use crate::conversion::{py_set, rev_pyiter_collect}; |
|
15 | use crate::conversion::{py_set, rev_pyiter_collect}; | |
16 | use cindex::Index; |
|
16 | use cindex::Index; | |
17 | use cpython::{ObjectProtocol, PyDict, PyModule, PyObject, PyResult, Python}; |
|
17 | use cpython::{ | |
|
18 | ObjectProtocol, PyDict, PyModule, PyObject, PyResult, Python, ToPyObject, | |||
|
19 | }; | |||
18 | use exceptions::GraphError; |
|
20 | use exceptions::GraphError; | |
19 | use hg::discovery::PartialDiscovery as CorePartialDiscovery; |
|
21 | use hg::discovery::PartialDiscovery as CorePartialDiscovery; | |
20 | use hg::Revision; |
|
22 | use hg::Revision; | |
21 |
|
23 | |||
22 | use std::cell::RefCell; |
|
24 | use std::cell::RefCell; | |
23 |
|
25 | |||
24 | py_class!(pub class PartialDiscovery |py| { |
|
26 | py_class!(pub class PartialDiscovery |py| { | |
25 | data inner: RefCell<Box<CorePartialDiscovery<Index>>>; |
|
27 | data inner: RefCell<Box<CorePartialDiscovery<Index>>>; | |
26 |
|
28 | |||
27 | def __new__( |
|
29 | def __new__( | |
28 | _cls, |
|
30 | _cls, | |
29 | index: PyObject, |
|
31 | index: PyObject, | |
30 | targetheads: PyObject |
|
32 | targetheads: PyObject | |
31 | ) -> PyResult<PartialDiscovery> { |
|
33 | ) -> PyResult<PartialDiscovery> { | |
32 | Self::create_instance( |
|
34 | Self::create_instance( | |
33 | py, |
|
35 | py, | |
34 | RefCell::new(Box::new(CorePartialDiscovery::new( |
|
36 | RefCell::new(Box::new(CorePartialDiscovery::new( | |
35 | Index::new(py, index)?, |
|
37 | Index::new(py, index)?, | |
36 | rev_pyiter_collect(py, &targetheads)?, |
|
38 | rev_pyiter_collect(py, &targetheads)?, | |
37 | ))) |
|
39 | ))) | |
38 | ) |
|
40 | ) | |
39 | } |
|
41 | } | |
40 |
|
42 | |||
41 | def addcommons(&self, commons: PyObject) -> PyResult<PyObject> { |
|
43 | def addcommons(&self, commons: PyObject) -> PyResult<PyObject> { | |
42 | let mut inner = self.inner(py).borrow_mut(); |
|
44 | let mut inner = self.inner(py).borrow_mut(); | |
43 | let commons_vec: Vec<Revision> = rev_pyiter_collect(py, &commons)?; |
|
45 | let commons_vec: Vec<Revision> = rev_pyiter_collect(py, &commons)?; | |
44 | inner.add_common_revisions(commons_vec) |
|
46 | inner.add_common_revisions(commons_vec) | |
45 | .map_err(|e| GraphError::pynew(py, e))?; |
|
47 | .map_err(|e| GraphError::pynew(py, e))?; | |
46 | Ok(py.None()) |
|
48 | Ok(py.None()) | |
47 | } |
|
49 | } | |
48 |
|
50 | |||
49 | def addmissings(&self, missings: PyObject) -> PyResult<PyObject> { |
|
51 | def addmissings(&self, missings: PyObject) -> PyResult<PyObject> { | |
50 | let mut inner = self.inner(py).borrow_mut(); |
|
52 | let mut inner = self.inner(py).borrow_mut(); | |
51 | let missings_vec: Vec<Revision> = rev_pyiter_collect(py, &missings)?; |
|
53 | let missings_vec: Vec<Revision> = rev_pyiter_collect(py, &missings)?; | |
52 | inner.add_missing_revisions(missings_vec) |
|
54 | inner.add_missing_revisions(missings_vec) | |
53 | .map_err(|e| GraphError::pynew(py, e))?; |
|
55 | .map_err(|e| GraphError::pynew(py, e))?; | |
54 | Ok(py.None()) |
|
56 | Ok(py.None()) | |
55 | } |
|
57 | } | |
56 |
|
58 | |||
57 | def addinfo(&self, sample: PyObject) -> PyResult<PyObject> { |
|
59 | def addinfo(&self, sample: PyObject) -> PyResult<PyObject> { | |
58 | let mut missing: Vec<Revision> = Vec::new(); |
|
60 | let mut missing: Vec<Revision> = Vec::new(); | |
59 | let mut common: Vec<Revision> = Vec::new(); |
|
61 | let mut common: Vec<Revision> = Vec::new(); | |
60 | for info in sample.iter(py)? { // info is a pair (Revision, bool) |
|
62 | for info in sample.iter(py)? { // info is a pair (Revision, bool) | |
61 | let mut revknown = info?.iter(py)?; |
|
63 | let mut revknown = info?.iter(py)?; | |
62 | let rev: Revision = revknown.next().unwrap()?.extract(py)?; |
|
64 | let rev: Revision = revknown.next().unwrap()?.extract(py)?; | |
63 | let known: bool = revknown.next().unwrap()?.extract(py)?; |
|
65 | let known: bool = revknown.next().unwrap()?.extract(py)?; | |
64 | if known { |
|
66 | if known { | |
65 | common.push(rev); |
|
67 | common.push(rev); | |
66 | } else { |
|
68 | } else { | |
67 | missing.push(rev); |
|
69 | missing.push(rev); | |
68 | } |
|
70 | } | |
69 | } |
|
71 | } | |
70 | let mut inner = self.inner(py).borrow_mut(); |
|
72 | let mut inner = self.inner(py).borrow_mut(); | |
71 | inner.add_common_revisions(common) |
|
73 | inner.add_common_revisions(common) | |
72 | .map_err(|e| GraphError::pynew(py, e))?; |
|
74 | .map_err(|e| GraphError::pynew(py, e))?; | |
73 | inner.add_missing_revisions(missing) |
|
75 | inner.add_missing_revisions(missing) | |
74 | .map_err(|e| GraphError::pynew(py, e))?; |
|
76 | .map_err(|e| GraphError::pynew(py, e))?; | |
75 | Ok(py.None()) |
|
77 | Ok(py.None()) | |
76 | } |
|
78 | } | |
77 |
|
79 | |||
78 | def hasinfo(&self) -> PyResult<bool> { |
|
80 | def hasinfo(&self) -> PyResult<bool> { | |
79 | Ok(self.inner(py).borrow().has_info()) |
|
81 | Ok(self.inner(py).borrow().has_info()) | |
80 | } |
|
82 | } | |
81 |
|
83 | |||
82 | def iscomplete(&self) -> PyResult<bool> { |
|
84 | def iscomplete(&self) -> PyResult<bool> { | |
83 | Ok(self.inner(py).borrow().is_complete()) |
|
85 | Ok(self.inner(py).borrow().is_complete()) | |
84 | } |
|
86 | } | |
85 |
|
87 | |||
|
88 | def stats(&self) -> PyResult<PyDict> { | |||
|
89 | let stats = self.inner(py).borrow().stats(); | |||
|
90 | let as_dict: PyDict = PyDict::new(py); | |||
|
91 | as_dict.set_item(py, "undecided", | |||
|
92 | stats.undecided.map(|l| l.to_py_object(py)) | |||
|
93 | .unwrap_or_else(|| py.None()))?; | |||
|
94 | Ok(as_dict) | |||
|
95 | } | |||
|
96 | ||||
86 | def commonheads(&self) -> PyResult<PyObject> { |
|
97 | def commonheads(&self) -> PyResult<PyObject> { | |
87 | py_set( |
|
98 | py_set( | |
88 | py, |
|
99 | py, | |
89 | &self.inner(py).borrow().common_heads() |
|
100 | &self.inner(py).borrow().common_heads() | |
90 | .map_err(|e| GraphError::pynew(py, e))? |
|
101 | .map_err(|e| GraphError::pynew(py, e))? | |
91 | ) |
|
102 | ) | |
92 | } |
|
103 | } | |
93 | }); |
|
104 | }); | |
94 |
|
105 | |||
95 | /// Create the module, with __package__ given from parent |
|
106 | /// Create the module, with __package__ given from parent | |
96 | pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> { |
|
107 | pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> { | |
97 | let dotted_name = &format!("{}.discovery", package); |
|
108 | let dotted_name = &format!("{}.discovery", package); | |
98 | let m = PyModule::new(py, dotted_name)?; |
|
109 | let m = PyModule::new(py, dotted_name)?; | |
99 | m.add(py, "__package__", package)?; |
|
110 | m.add(py, "__package__", package)?; | |
100 | m.add( |
|
111 | m.add( | |
101 | py, |
|
112 | py, | |
102 | "__doc__", |
|
113 | "__doc__", | |
103 | "Discovery of common node sets - Rust implementation", |
|
114 | "Discovery of common node sets - Rust implementation", | |
104 | )?; |
|
115 | )?; | |
105 | m.add_class::<PartialDiscovery>(py)?; |
|
116 | m.add_class::<PartialDiscovery>(py)?; | |
106 |
|
117 | |||
107 | let sys = PyModule::import(py, "sys")?; |
|
118 | let sys = PyModule::import(py, "sys")?; | |
108 | let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?; |
|
119 | let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?; | |
109 | sys_modules.set_item(py, dotted_name, &m)?; |
|
120 | sys_modules.set_item(py, dotted_name, &m)?; | |
110 | // Example C code (see pyexpat.c and import.c) will "give away the |
|
121 | // Example C code (see pyexpat.c and import.c) will "give away the | |
111 | // reference", but we won't because it will be consumed once the |
|
122 | // reference", but we won't because it will be consumed once the | |
112 | // Rust PyObject is dropped. |
|
123 | // Rust PyObject is dropped. | |
113 | Ok(m) |
|
124 | Ok(m) | |
114 | } |
|
125 | } |
@@ -1,103 +1,111 b'' | |||||
1 | from __future__ import absolute_import |
|
1 | from __future__ import absolute_import | |
2 | import unittest |
|
2 | import unittest | |
3 |
|
3 | |||
4 | try: |
|
4 | try: | |
5 | from mercurial import rustext |
|
5 | from mercurial import rustext | |
6 | rustext.__name__ # trigger immediate actual import |
|
6 | rustext.__name__ # trigger immediate actual import | |
7 | except ImportError: |
|
7 | except ImportError: | |
8 | rustext = None |
|
8 | rustext = None | |
9 | else: |
|
9 | else: | |
10 | # this would fail already without appropriate ancestor.__package__ |
|
10 | # this would fail already without appropriate ancestor.__package__ | |
11 | from mercurial.rustext.discovery import ( |
|
11 | from mercurial.rustext.discovery import ( | |
12 | PartialDiscovery, |
|
12 | PartialDiscovery, | |
13 | ) |
|
13 | ) | |
14 |
|
14 | |||
15 | try: |
|
15 | try: | |
16 | from mercurial.cext import parsers as cparsers |
|
16 | from mercurial.cext import parsers as cparsers | |
17 | except ImportError: |
|
17 | except ImportError: | |
18 | cparsers = None |
|
18 | cparsers = None | |
19 |
|
19 | |||
20 | # picked from test-parse-index2, copied rather than imported |
|
20 | # picked from test-parse-index2, copied rather than imported | |
21 | # so that it stays stable even if test-parse-index2 changes or disappears. |
|
21 | # so that it stays stable even if test-parse-index2 changes or disappears. | |
22 | data_non_inlined = ( |
|
22 | data_non_inlined = ( | |
23 | b'\x00\x00\x00\x01\x00\x00\x00\x00\x00\x01D\x19' |
|
23 | b'\x00\x00\x00\x01\x00\x00\x00\x00\x00\x01D\x19' | |
24 | b'\x00\x07e\x12\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff' |
|
24 | b'\x00\x07e\x12\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff' | |
25 | b'\xff\xff\xff\xff\xd1\xf4\xbb\xb0\xbe\xfc\x13\xbd\x8c\xd3\x9d' |
|
25 | b'\xff\xff\xff\xff\xd1\xf4\xbb\xb0\xbe\xfc\x13\xbd\x8c\xd3\x9d' | |
26 | b'\x0f\xcd\xd9;\x8c\x07\x8cJ/\x00\x00\x00\x00\x00\x00\x00\x00\x00' |
|
26 | b'\x0f\xcd\xd9;\x8c\x07\x8cJ/\x00\x00\x00\x00\x00\x00\x00\x00\x00' | |
27 | b'\x00\x00\x00\x00\x00\x00\x01D\x19\x00\x00\x00\x00\x00\xdf\x00' |
|
27 | b'\x00\x00\x00\x00\x00\x00\x01D\x19\x00\x00\x00\x00\x00\xdf\x00' | |
28 | b'\x00\x01q\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\xff' |
|
28 | b'\x00\x01q\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\xff' | |
29 | b'\xff\xff\xff\xc1\x12\xb9\x04\x96\xa4Z1t\x91\xdfsJ\x90\xf0\x9bh' |
|
29 | b'\xff\xff\xff\xc1\x12\xb9\x04\x96\xa4Z1t\x91\xdfsJ\x90\xf0\x9bh' | |
30 | b'\x07l&\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' |
|
30 | b'\x07l&\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' | |
31 | b'\x00\x01D\xf8\x00\x00\x00\x00\x01\x1b\x00\x00\x01\xb8\x00\x00' |
|
31 | b'\x00\x01D\xf8\x00\x00\x00\x00\x01\x1b\x00\x00\x01\xb8\x00\x00' | |
32 | b'\x00\x01\x00\x00\x00\x02\x00\x00\x00\x01\xff\xff\xff\xff\x02\n' |
|
32 | b'\x00\x01\x00\x00\x00\x02\x00\x00\x00\x01\xff\xff\xff\xff\x02\n' | |
33 | b'\x0e\xc6&\xa1\x92\xae6\x0b\x02i\xfe-\xe5\xbao\x05\xd1\xe7\x00' |
|
33 | b'\x0e\xc6&\xa1\x92\xae6\x0b\x02i\xfe-\xe5\xbao\x05\xd1\xe7\x00' | |
34 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01F' |
|
34 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01F' | |
35 | b'\x13\x00\x00\x00\x00\x01\xec\x00\x00\x03\x06\x00\x00\x00\x01' |
|
35 | b'\x13\x00\x00\x00\x00\x01\xec\x00\x00\x03\x06\x00\x00\x00\x01' | |
36 | b'\x00\x00\x00\x03\x00\x00\x00\x02\xff\xff\xff\xff\x12\xcb\xeby1' |
|
36 | b'\x00\x00\x00\x03\x00\x00\x00\x02\xff\xff\xff\xff\x12\xcb\xeby1' | |
37 | b'\xb6\r\x98B\xcb\x07\xbd`\x8f\x92\xd9\xc4\x84\xbdK\x00\x00\x00' |
|
37 | b'\xb6\r\x98B\xcb\x07\xbd`\x8f\x92\xd9\xc4\x84\xbdK\x00\x00\x00' | |
38 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00' |
|
38 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00' | |
39 | ) |
|
39 | ) | |
40 |
|
40 | |||
41 |
|
41 | |||
42 | @unittest.skipIf(rustext is None or cparsers is None, |
|
42 | @unittest.skipIf(rustext is None or cparsers is None, | |
43 | "rustext or the C Extension parsers module " |
|
43 | "rustext or the C Extension parsers module " | |
44 | "discovery relies on is not available") |
|
44 | "discovery relies on is not available") | |
45 | class rustdiscoverytest(unittest.TestCase): |
|
45 | class rustdiscoverytest(unittest.TestCase): | |
46 | """Test the correctness of binding to Rust code. |
|
46 | """Test the correctness of binding to Rust code. | |
47 |
|
47 | |||
48 | This test is merely for the binding to Rust itself: extraction of |
|
48 | This test is merely for the binding to Rust itself: extraction of | |
49 | Python variable, giving back the results etc. |
|
49 | Python variable, giving back the results etc. | |
50 |
|
50 | |||
51 | It is not meant to test the algorithmic correctness of the provided |
|
51 | It is not meant to test the algorithmic correctness of the provided | |
52 | methods. Hence the very simple embedded index data is good enough. |
|
52 | methods. Hence the very simple embedded index data is good enough. | |
53 |
|
53 | |||
54 | Algorithmic correctness is asserted by the Rust unit tests. |
|
54 | Algorithmic correctness is asserted by the Rust unit tests. | |
55 | """ |
|
55 | """ | |
56 |
|
56 | |||
57 | def parseindex(self): |
|
57 | def parseindex(self): | |
58 | return cparsers.parse_index2(data_non_inlined, False)[0] |
|
58 | return cparsers.parse_index2(data_non_inlined, False)[0] | |
59 |
|
59 | |||
60 | def testindex(self): |
|
60 | def testindex(self): | |
61 | idx = self.parseindex() |
|
61 | idx = self.parseindex() | |
62 | # checking our assumptions about the index binary data: |
|
62 | # checking our assumptions about the index binary data: | |
63 | self.assertEqual({i: (r[5], r[6]) for i, r in enumerate(idx)}, |
|
63 | self.assertEqual({i: (r[5], r[6]) for i, r in enumerate(idx)}, | |
64 | {0: (-1, -1), |
|
64 | {0: (-1, -1), | |
65 | 1: (0, -1), |
|
65 | 1: (0, -1), | |
66 | 2: (1, -1), |
|
66 | 2: (1, -1), | |
67 | 3: (2, -1)}) |
|
67 | 3: (2, -1)}) | |
68 |
|
68 | |||
69 | def testaddcommonsmissings(self): |
|
69 | def testaddcommonsmissings(self): | |
70 | idx = self.parseindex() |
|
70 | idx = self.parseindex() | |
71 | disco = PartialDiscovery(idx, [3]) |
|
71 | disco = PartialDiscovery(idx, [3]) | |
72 | self.assertFalse(disco.hasinfo()) |
|
72 | self.assertFalse(disco.hasinfo()) | |
73 | self.assertFalse(disco.iscomplete()) |
|
73 | self.assertFalse(disco.iscomplete()) | |
74 |
|
74 | |||
75 | disco.addcommons([1]) |
|
75 | disco.addcommons([1]) | |
76 | self.assertTrue(disco.hasinfo()) |
|
76 | self.assertTrue(disco.hasinfo()) | |
77 | self.assertFalse(disco.iscomplete()) |
|
77 | self.assertFalse(disco.iscomplete()) | |
78 |
|
78 | |||
79 | disco.addmissings([2]) |
|
79 | disco.addmissings([2]) | |
80 | self.assertTrue(disco.hasinfo()) |
|
80 | self.assertTrue(disco.hasinfo()) | |
81 | self.assertTrue(disco.iscomplete()) |
|
81 | self.assertTrue(disco.iscomplete()) | |
82 |
|
82 | |||
83 | self.assertEqual(disco.commonheads(), {1}) |
|
83 | self.assertEqual(disco.commonheads(), {1}) | |
84 |
|
84 | |||
|
85 | def testaddmissingsstats(self): | |||
|
86 | idx = self.parseindex() | |||
|
87 | disco = PartialDiscovery(idx, [3]) | |||
|
88 | self.assertIsNone(disco.stats()['undecided'], None) | |||
|
89 | ||||
|
90 | disco.addmissings([2]) | |||
|
91 | self.assertEqual(disco.stats()['undecided'], 2) | |||
|
92 | ||||
85 | def testaddinfocommonfirst(self): |
|
93 | def testaddinfocommonfirst(self): | |
86 | idx = self.parseindex() |
|
94 | idx = self.parseindex() | |
87 | disco = PartialDiscovery(idx, [3]) |
|
95 | disco = PartialDiscovery(idx, [3]) | |
88 | disco.addinfo([(1, True), (2, False)]) |
|
96 | disco.addinfo([(1, True), (2, False)]) | |
89 | self.assertTrue(disco.hasinfo()) |
|
97 | self.assertTrue(disco.hasinfo()) | |
90 | self.assertTrue(disco.iscomplete()) |
|
98 | self.assertTrue(disco.iscomplete()) | |
91 | self.assertEqual(disco.commonheads(), {1}) |
|
99 | self.assertEqual(disco.commonheads(), {1}) | |
92 |
|
100 | |||
93 | def testaddinfomissingfirst(self): |
|
101 | def testaddinfomissingfirst(self): | |
94 | idx = self.parseindex() |
|
102 | idx = self.parseindex() | |
95 | disco = PartialDiscovery(idx, [3]) |
|
103 | disco = PartialDiscovery(idx, [3]) | |
96 | disco.addinfo([(2, False), (1, True)]) |
|
104 | disco.addinfo([(2, False), (1, True)]) | |
97 | self.assertTrue(disco.hasinfo()) |
|
105 | self.assertTrue(disco.hasinfo()) | |
98 | self.assertTrue(disco.iscomplete()) |
|
106 | self.assertTrue(disco.iscomplete()) | |
99 | self.assertEqual(disco.commonheads(), {1}) |
|
107 | self.assertEqual(disco.commonheads(), {1}) | |
100 |
|
108 | |||
101 | if __name__ == '__main__': |
|
109 | if __name__ == '__main__': | |
102 | import silenttestrunner |
|
110 | import silenttestrunner | |
103 | silenttestrunner.main(__name__) |
|
111 | silenttestrunner.main(__name__) |
General Comments 0
You need to be logged in to leave comments.
Login now