Show More
@@ -92,11 +92,19 b' def _updatesample(revs, heads, sample, p' | |||||
92 | dist.setdefault(p, d + 1) |
|
92 | dist.setdefault(p, d + 1) | |
93 | visit.append(p) |
|
93 | visit.append(p) | |
94 |
|
94 | |||
95 | def _limitsample(sample, desiredlen): |
|
95 | def _limitsample(sample, desiredlen, randomize=True): | |
96 |
"""return a random subset of sample of at most desiredlen item |
|
96 | """return a random subset of sample of at most desiredlen item. | |
97 | if len(sample) > desiredlen: |
|
97 | ||
98 | sample = set(random.sample(sample, desiredlen)) |
|
98 | If randomize is False, though, a deterministic subset is returned. | |
99 | return sample |
|
99 | This is meant for integration tests. | |
|
100 | """ | |||
|
101 | if len(sample) <= desiredlen: | |||
|
102 | return sample | |||
|
103 | if randomize: | |||
|
104 | return set(random.sample(sample, desiredlen)) | |||
|
105 | sample = list(sample) | |||
|
106 | sample.sort() | |||
|
107 | return set(sample[:desiredlen]) | |||
100 |
|
108 | |||
101 | class partialdiscovery(object): |
|
109 | class partialdiscovery(object): | |
102 | """an object representing ongoing discovery |
|
110 | """an object representing ongoing discovery | |
@@ -110,7 +118,7 b' class partialdiscovery(object):' | |||||
110 | (all tracked revisions are known locally) |
|
118 | (all tracked revisions are known locally) | |
111 | """ |
|
119 | """ | |
112 |
|
120 | |||
113 | def __init__(self, repo, targetheads, respectsize): |
|
121 | def __init__(self, repo, targetheads, respectsize, randomize=True): | |
114 | self._repo = repo |
|
122 | self._repo = repo | |
115 | self._targetheads = targetheads |
|
123 | self._targetheads = targetheads | |
116 | self._common = repo.changelog.incrementalmissingrevs() |
|
124 | self._common = repo.changelog.incrementalmissingrevs() | |
@@ -118,6 +126,7 b' class partialdiscovery(object):' | |||||
118 | self.missing = set() |
|
126 | self.missing = set() | |
119 | self._childrenmap = None |
|
127 | self._childrenmap = None | |
120 | self._respectsize = respectsize |
|
128 | self._respectsize = respectsize | |
|
129 | self.randomize = randomize | |||
121 |
|
130 | |||
122 | def addcommons(self, commons): |
|
131 | def addcommons(self, commons): | |
123 | """register nodes known as common""" |
|
132 | """register nodes known as common""" | |
@@ -222,7 +231,7 b' class partialdiscovery(object):' | |||||
222 | sample = set(self._repo.revs('heads(%ld)', revs)) |
|
231 | sample = set(self._repo.revs('heads(%ld)', revs)) | |
223 |
|
232 | |||
224 | if len(sample) >= size: |
|
233 | if len(sample) >= size: | |
225 | return _limitsample(sample, size) |
|
234 | return _limitsample(sample, size, randomize=self.randomize) | |
226 |
|
235 | |||
227 | _updatesample(None, headrevs, sample, self._parentsgetter(), |
|
236 | _updatesample(None, headrevs, sample, self._parentsgetter(), | |
228 | quicksamplesize=size) |
|
237 | quicksamplesize=size) | |
@@ -249,10 +258,15 b' class partialdiscovery(object):' | |||||
249 | if not self._respectsize: |
|
258 | if not self._respectsize: | |
250 | size = max(size, min(len(revsroots), len(revsheads))) |
|
259 | size = max(size, min(len(revsroots), len(revsheads))) | |
251 |
|
260 | |||
252 | sample = _limitsample(sample, size) |
|
261 | sample = _limitsample(sample, size, randomize=self.randomize) | |
253 | if len(sample) < size: |
|
262 | if len(sample) < size: | |
254 | more = size - len(sample) |
|
263 | more = size - len(sample) | |
255 |
|
|
264 | takefrom = list(revs - sample) | |
|
265 | if self.randomize: | |||
|
266 | sample.update(random.sample(takefrom, more)) | |||
|
267 | else: | |||
|
268 | takefrom.sort() | |||
|
269 | sample.update(takefrom[:more]) | |||
256 | return sample |
|
270 | return sample | |
257 |
|
271 | |||
258 | def findcommonheads(ui, local, remote, |
|
272 | def findcommonheads(ui, local, remote, |
@@ -31,6 +31,7 b' pub struct PartialDiscovery<G: Graph + C' | |||||
31 | missing: HashSet<Revision>, |
|
31 | missing: HashSet<Revision>, | |
32 | rng: Rng, |
|
32 | rng: Rng, | |
33 | respect_size: bool, |
|
33 | respect_size: bool, | |
|
34 | randomize: bool, | |||
34 | } |
|
35 | } | |
35 |
|
36 | |||
36 | pub struct DiscoveryStats { |
|
37 | pub struct DiscoveryStats { | |
@@ -151,14 +152,26 b' impl<G: Graph + Clone> PartialDiscovery<' | |||||
151 | /// will interpret the size argument requested by the caller. If it's |
|
152 | /// will interpret the size argument requested by the caller. If it's | |
152 | /// `false`, they are allowed to produce a sample whose size is more |
|
153 | /// `false`, they are allowed to produce a sample whose size is more | |
153 | /// appropriate to the situation (typically bigger). |
|
154 | /// appropriate to the situation (typically bigger). | |
|
155 | /// | |||
|
156 | /// The `randomize` boolean affects sampling, and specifically how | |||
|
157 | /// limiting or last-minute expanding is been done: | |||
|
158 | /// | |||
|
159 | /// If `true`, both will perform random picking from `self.undecided`. | |||
|
160 | /// This is currently the best for actual discoveries. | |||
|
161 | /// | |||
|
162 | /// If `false`, a reproductible picking strategy is performed. This is | |||
|
163 | /// useful for integration tests. | |||
154 | pub fn new( |
|
164 | pub fn new( | |
155 | graph: G, |
|
165 | graph: G, | |
156 | target_heads: Vec<Revision>, |
|
166 | target_heads: Vec<Revision>, | |
157 | respect_size: bool, |
|
167 | respect_size: bool, | |
|
168 | randomize: bool, | |||
158 | ) -> Self { |
|
169 | ) -> Self { | |
159 | let mut seed: [u8; 16] = [0; 16]; |
|
170 | let mut seed: [u8; 16] = [0; 16]; | |
160 | thread_rng().fill_bytes(&mut seed); |
|
171 | if randomize { | |
161 | Self::new_with_seed(graph, target_heads, seed, respect_size) |
|
172 | thread_rng().fill_bytes(&mut seed); | |
|
173 | } | |||
|
174 | Self::new_with_seed(graph, target_heads, seed, respect_size, randomize) | |||
162 | } |
|
175 | } | |
163 |
|
176 | |||
164 | pub fn new_with_seed( |
|
177 | pub fn new_with_seed( | |
@@ -166,6 +179,7 b' impl<G: Graph + Clone> PartialDiscovery<' | |||||
166 | target_heads: Vec<Revision>, |
|
179 | target_heads: Vec<Revision>, | |
167 | seed: [u8; 16], |
|
180 | seed: [u8; 16], | |
168 | respect_size: bool, |
|
181 | respect_size: bool, | |
|
182 | randomize: bool, | |||
169 | ) -> Self { |
|
183 | ) -> Self { | |
170 | PartialDiscovery { |
|
184 | PartialDiscovery { | |
171 | undecided: None, |
|
185 | undecided: None, | |
@@ -176,6 +190,7 b' impl<G: Graph + Clone> PartialDiscovery<' | |||||
176 | missing: HashSet::new(), |
|
190 | missing: HashSet::new(), | |
177 | rng: Rng::from_seed(seed), |
|
191 | rng: Rng::from_seed(seed), | |
178 | respect_size: respect_size, |
|
192 | respect_size: respect_size, | |
|
193 | randomize: randomize, | |||
179 | } |
|
194 | } | |
180 | } |
|
195 | } | |
181 |
|
196 | |||
@@ -186,6 +201,11 b' impl<G: Graph + Clone> PartialDiscovery<' | |||||
186 | mut sample: Vec<Revision>, |
|
201 | mut sample: Vec<Revision>, | |
187 | size: usize, |
|
202 | size: usize, | |
188 | ) -> Vec<Revision> { |
|
203 | ) -> Vec<Revision> { | |
|
204 | if !self.randomize { | |||
|
205 | sample.sort(); | |||
|
206 | sample.truncate(size); | |||
|
207 | return sample; | |||
|
208 | } | |||
189 | let sample_len = sample.len(); |
|
209 | let sample_len = sample.len(); | |
190 | if sample_len <= size { |
|
210 | if sample_len <= size { | |
191 | return sample; |
|
211 | return sample; | |
@@ -436,13 +456,15 b' mod tests {' | |||||
436 |
|
456 | |||
437 | /// A PartialDiscovery as for pushing all the heads of `SampleGraph` |
|
457 | /// A PartialDiscovery as for pushing all the heads of `SampleGraph` | |
438 | /// |
|
458 | /// | |
439 |
/// To avoid actual randomness in tests, we give it a fixed |
|
459 | /// To avoid actual randomness in these tests, we give it a fixed | |
|
460 | /// random seed, but by default we'll test the random version. | |||
440 | fn full_disco() -> PartialDiscovery<SampleGraph> { |
|
461 | fn full_disco() -> PartialDiscovery<SampleGraph> { | |
441 | PartialDiscovery::new_with_seed( |
|
462 | PartialDiscovery::new_with_seed( | |
442 | SampleGraph, |
|
463 | SampleGraph, | |
443 | vec![10, 11, 12, 13], |
|
464 | vec![10, 11, 12, 13], | |
444 | [0; 16], |
|
465 | [0; 16], | |
445 | true, |
|
466 | true, | |
|
467 | true, | |||
446 | ) |
|
468 | ) | |
447 | } |
|
469 | } | |
448 |
|
470 | |||
@@ -450,7 +472,13 b' mod tests {' | |||||
450 | /// |
|
472 | /// | |
451 | /// To avoid actual randomness in tests, we give it a fixed random seed. |
|
473 | /// To avoid actual randomness in tests, we give it a fixed random seed. | |
452 | fn disco12() -> PartialDiscovery<SampleGraph> { |
|
474 | fn disco12() -> PartialDiscovery<SampleGraph> { | |
453 |
PartialDiscovery::new_with_seed( |
|
475 | PartialDiscovery::new_with_seed( | |
|
476 | SampleGraph, | |||
|
477 | vec![12], | |||
|
478 | [0; 16], | |||
|
479 | true, | |||
|
480 | true, | |||
|
481 | ) | |||
454 | } |
|
482 | } | |
455 |
|
483 | |||
456 | fn sorted_undecided( |
|
484 | fn sorted_undecided( | |
@@ -535,6 +563,16 b' mod tests {' | |||||
535 | } |
|
563 | } | |
536 |
|
564 | |||
537 | #[test] |
|
565 | #[test] | |
|
566 | fn test_limit_sample_no_random() { | |||
|
567 | let mut disco = full_disco(); | |||
|
568 | disco.randomize = false; | |||
|
569 | assert_eq!( | |||
|
570 | disco.limit_sample(vec![1, 8, 13, 5, 7, 3], 4), | |||
|
571 | vec![1, 3, 5, 7] | |||
|
572 | ); | |||
|
573 | } | |||
|
574 | ||||
|
575 | #[test] | |||
538 | fn test_quick_sample_enough_undecided_heads() -> Result<(), GraphError> { |
|
576 | fn test_quick_sample_enough_undecided_heads() -> Result<(), GraphError> { | |
539 | let mut disco = full_disco(); |
|
577 | let mut disco = full_disco(); | |
540 | disco.undecided = Some((1..=13).collect()); |
|
578 | disco.undecided = Some((1..=13).collect()); |
@@ -36,7 +36,8 b' py_class!(pub class PartialDiscovery |py' | |||||
36 | _cls, |
|
36 | _cls, | |
37 | repo: PyObject, |
|
37 | repo: PyObject, | |
38 | targetheads: PyObject, |
|
38 | targetheads: PyObject, | |
39 | respectsize: bool |
|
39 | respectsize: bool, | |
|
40 | randomize: bool = true | |||
40 | ) -> PyResult<PartialDiscovery> { |
|
41 | ) -> PyResult<PartialDiscovery> { | |
41 | let index = repo.getattr(py, "changelog")?.getattr(py, "index")?; |
|
42 | let index = repo.getattr(py, "changelog")?.getattr(py, "index")?; | |
42 | Self::create_instance( |
|
43 | Self::create_instance( | |
@@ -44,7 +45,8 b' py_class!(pub class PartialDiscovery |py' | |||||
44 | RefCell::new(Box::new(CorePartialDiscovery::new( |
|
45 | RefCell::new(Box::new(CorePartialDiscovery::new( | |
45 | Index::new(py, index)?, |
|
46 | Index::new(py, index)?, | |
46 | rev_pyiter_collect(py, &targetheads)?, |
|
47 | rev_pyiter_collect(py, &targetheads)?, | |
47 | respectsize |
|
48 | respectsize, | |
|
49 | randomize, | |||
48 | ))) |
|
50 | ))) | |
49 | ) |
|
51 | ) | |
50 | } |
|
52 | } |
@@ -103,6 +103,9 b' class rustdiscoverytest(unittest.TestCas' | |||||
103 | self.assertTrue(disco.iscomplete()) |
|
103 | self.assertTrue(disco.iscomplete()) | |
104 | self.assertEqual(disco.commonheads(), {1}) |
|
104 | self.assertEqual(disco.commonheads(), {1}) | |
105 |
|
105 | |||
|
106 | def testinitnorandom(self): | |||
|
107 | PartialDiscovery(self.repo(), [3], True, randomize=False) | |||
|
108 | ||||
106 | if __name__ == '__main__': |
|
109 | if __name__ == '__main__': | |
107 | import silenttestrunner |
|
110 | import silenttestrunner | |
108 | silenttestrunner.main(__name__) |
|
111 | silenttestrunner.main(__name__) |
General Comments 0
You need to be logged in to leave comments.
Login now