##// END OF EJS Templates
smartset: add some doctests...
Jun Wu -
r31019:74f77f1c default
parent child Browse files
Show More
@@ -1,986 +1,1031 b''
1 1 # smartset.py - data structure for revision set
2 2 #
3 3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
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 from __future__ import absolute_import
9 9
10 10 from . import (
11 11 util,
12 12 )
13 13
14 14 def _formatsetrepr(r):
15 15 """Format an optional printable representation of a set
16 16
17 17 ======== =================================
18 18 type(r) example
19 19 ======== =================================
20 20 tuple ('<not %r>', other)
21 21 str '<branch closed>'
22 22 callable lambda: '<branch %r>' % sorted(b)
23 23 object other
24 24 ======== =================================
25 25 """
26 26 if r is None:
27 27 return ''
28 28 elif isinstance(r, tuple):
29 29 return r[0] % r[1:]
30 30 elif isinstance(r, str):
31 31 return r
32 32 elif callable(r):
33 33 return r()
34 34 else:
35 35 return repr(r)
36 36
37 37 class abstractsmartset(object):
38 38
39 39 def __nonzero__(self):
40 40 """True if the smartset is not empty"""
41 41 raise NotImplementedError()
42 42
43 43 def __contains__(self, rev):
44 44 """provide fast membership testing"""
45 45 raise NotImplementedError()
46 46
47 47 def __iter__(self):
48 48 """iterate the set in the order it is supposed to be iterated"""
49 49 raise NotImplementedError()
50 50
51 51 # Attributes containing a function to perform a fast iteration in a given
52 52 # direction. A smartset can have none, one, or both defined.
53 53 #
54 54 # Default value is None instead of a function returning None to avoid
55 55 # initializing an iterator just for testing if a fast method exists.
56 56 fastasc = None
57 57 fastdesc = None
58 58
59 59 def isascending(self):
60 60 """True if the set will iterate in ascending order"""
61 61 raise NotImplementedError()
62 62
63 63 def isdescending(self):
64 64 """True if the set will iterate in descending order"""
65 65 raise NotImplementedError()
66 66
67 67 def istopo(self):
68 68 """True if the set will iterate in topographical order"""
69 69 raise NotImplementedError()
70 70
71 71 def min(self):
72 72 """return the minimum element in the set"""
73 73 if self.fastasc is None:
74 74 v = min(self)
75 75 else:
76 76 for v in self.fastasc():
77 77 break
78 78 else:
79 79 raise ValueError('arg is an empty sequence')
80 80 self.min = lambda: v
81 81 return v
82 82
83 83 def max(self):
84 84 """return the maximum element in the set"""
85 85 if self.fastdesc is None:
86 86 return max(self)
87 87 else:
88 88 for v in self.fastdesc():
89 89 break
90 90 else:
91 91 raise ValueError('arg is an empty sequence')
92 92 self.max = lambda: v
93 93 return v
94 94
95 95 def first(self):
96 96 """return the first element in the set (user iteration perspective)
97 97
98 98 Return None if the set is empty"""
99 99 raise NotImplementedError()
100 100
101 101 def last(self):
102 102 """return the last element in the set (user iteration perspective)
103 103
104 104 Return None if the set is empty"""
105 105 raise NotImplementedError()
106 106
107 107 def __len__(self):
108 108 """return the length of the smartsets
109 109
110 110 This can be expensive on smartset that could be lazy otherwise."""
111 111 raise NotImplementedError()
112 112
113 113 def reverse(self):
114 114 """reverse the expected iteration order"""
115 115 raise NotImplementedError()
116 116
117 117 def sort(self, reverse=True):
118 118 """get the set to iterate in an ascending or descending order"""
119 119 raise NotImplementedError()
120 120
121 121 def __and__(self, other):
122 122 """Returns a new object with the intersection of the two collections.
123 123
124 124 This is part of the mandatory API for smartset."""
125 125 if isinstance(other, fullreposet):
126 126 return self
127 127 return self.filter(other.__contains__, condrepr=other, cache=False)
128 128
129 129 def __add__(self, other):
130 130 """Returns a new object with the union of the two collections.
131 131
132 132 This is part of the mandatory API for smartset."""
133 133 return addset(self, other)
134 134
135 135 def __sub__(self, other):
136 136 """Returns a new object with the substraction of the two collections.
137 137
138 138 This is part of the mandatory API for smartset."""
139 139 c = other.__contains__
140 140 return self.filter(lambda r: not c(r), condrepr=('<not %r>', other),
141 141 cache=False)
142 142
143 143 def filter(self, condition, condrepr=None, cache=True):
144 144 """Returns this smartset filtered by condition as a new smartset.
145 145
146 146 `condition` is a callable which takes a revision number and returns a
147 147 boolean. Optional `condrepr` provides a printable representation of
148 148 the given `condition`.
149 149
150 150 This is part of the mandatory API for smartset."""
151 151 # builtin cannot be cached. but do not needs to
152 152 if cache and util.safehasattr(condition, 'func_code'):
153 153 condition = util.cachefunc(condition)
154 154 return filteredset(self, condition, condrepr)
155 155
156 156 class baseset(abstractsmartset):
157 157 """Basic data structure that represents a revset and contains the basic
158 158 operation that it should be able to perform.
159 159
160 160 Every method in this class should be implemented by any smartset class.
161
162 This class could be constructed by an (unordered) set, or an (ordered)
163 list-like object. If a set is provided, it'll be sorted lazily.
164
165 >>> x = [4, 0, 7, 6]
166 >>> y = [5, 6, 7, 3]
167
168 Construct by a set:
169 >>> xs = baseset(set(x))
170 >>> ys = baseset(set(y))
171 >>> [list(i) for i in [xs + ys, xs & ys, xs - ys]]
172 [[0, 4, 6, 7, 3, 5], [6, 7], [0, 4]]
173 >>> [type(i).__name__ for i in [xs + ys, xs & ys, xs - ys]]
174 ['addset', 'filteredset', 'filteredset']
175
176 Construct by a list-like:
177 >>> xs = baseset(x)
178 >>> ys = baseset(i for i in y)
179 >>> [list(i) for i in [xs + ys, xs & ys, xs - ys]]
180 [[4, 0, 7, 6, 5, 3], [7, 6], [4, 0]]
181 >>> [type(i).__name__ for i in [xs + ys, xs & ys, xs - ys]]
182 ['addset', 'filteredset', 'filteredset']
183
184 Populate "_set" fields in the lists so set optimization may be used:
185 >>> [1 in xs, 3 in ys]
186 [False, True]
187
188 Without sort(), results won't be changed:
189 >>> [list(i) for i in [xs + ys, xs & ys, xs - ys]]
190 [[4, 0, 7, 6, 5, 3], [7, 6], [4, 0]]
191 >>> [type(i).__name__ for i in [xs + ys, xs & ys, xs - ys]]
192 ['addset', 'filteredset', 'filteredset']
193
194 With sort():
195 >>> xs.sort(reverse=True)
196 >>> [list(i) for i in [xs + ys, xs & ys, xs - ys]]
197 [[7, 6, 4, 0, 5, 3], [7, 6], [4, 0]]
198 >>> [type(i).__name__ for i in [xs + ys, xs & ys, xs - ys]]
199 ['addset', 'filteredset', 'filteredset']
200
201 >>> ys.sort()
202 >>> [list(i) for i in [xs + ys, xs & ys, xs - ys]]
203 [[7, 6, 4, 0, 3, 5], [7, 6], [4, 0]]
204 >>> [type(i).__name__ for i in [xs + ys, xs & ys, xs - ys]]
205 ['addset', 'filteredset', 'filteredset']
161 206 """
162 207 def __init__(self, data=(), datarepr=None, istopo=False):
163 208 """
164 209 datarepr: a tuple of (format, obj, ...), a function or an object that
165 210 provides a printable representation of the given data.
166 211 """
167 212 self._ascending = None
168 213 self._istopo = istopo
169 214 if not isinstance(data, list):
170 215 if isinstance(data, set):
171 216 self._set = data
172 217 # set has no order we pick one for stability purpose
173 218 self._ascending = True
174 219 # converting set to list has a cost, do it lazily
175 220 data = None
176 221 else:
177 222 data = list(data)
178 223 if data is not None:
179 224 self._list = data
180 225 self._datarepr = datarepr
181 226
182 227 @util.propertycache
183 228 def _set(self):
184 229 return set(self._list)
185 230
186 231 @util.propertycache
187 232 def _asclist(self):
188 233 asclist = self._list[:]
189 234 asclist.sort()
190 235 return asclist
191 236
192 237 @util.propertycache
193 238 def _list(self):
194 239 # _list is only lazily constructed if we have _set
195 240 assert '_set' in self.__dict__
196 241 return list(self._set)
197 242
198 243 def __iter__(self):
199 244 if self._ascending is None:
200 245 return iter(self._list)
201 246 elif self._ascending:
202 247 return iter(self._asclist)
203 248 else:
204 249 return reversed(self._asclist)
205 250
206 251 def fastasc(self):
207 252 return iter(self._asclist)
208 253
209 254 def fastdesc(self):
210 255 return reversed(self._asclist)
211 256
212 257 @util.propertycache
213 258 def __contains__(self):
214 259 return self._set.__contains__
215 260
216 261 def __nonzero__(self):
217 262 return bool(len(self))
218 263
219 264 def sort(self, reverse=False):
220 265 self._ascending = not bool(reverse)
221 266 self._istopo = False
222 267
223 268 def reverse(self):
224 269 if self._ascending is None:
225 270 self._list.reverse()
226 271 else:
227 272 self._ascending = not self._ascending
228 273 self._istopo = False
229 274
230 275 def __len__(self):
231 276 if '_list' in self.__dict__:
232 277 return len(self._list)
233 278 else:
234 279 return len(self._set)
235 280
236 281 def isascending(self):
237 282 """Returns True if the collection is ascending order, False if not.
238 283
239 284 This is part of the mandatory API for smartset."""
240 285 if len(self) <= 1:
241 286 return True
242 287 return self._ascending is not None and self._ascending
243 288
244 289 def isdescending(self):
245 290 """Returns True if the collection is descending order, False if not.
246 291
247 292 This is part of the mandatory API for smartset."""
248 293 if len(self) <= 1:
249 294 return True
250 295 return self._ascending is not None and not self._ascending
251 296
252 297 def istopo(self):
253 298 """Is the collection is in topographical order or not.
254 299
255 300 This is part of the mandatory API for smartset."""
256 301 if len(self) <= 1:
257 302 return True
258 303 return self._istopo
259 304
260 305 def first(self):
261 306 if self:
262 307 if self._ascending is None:
263 308 return self._list[0]
264 309 elif self._ascending:
265 310 return self._asclist[0]
266 311 else:
267 312 return self._asclist[-1]
268 313 return None
269 314
270 315 def last(self):
271 316 if self:
272 317 if self._ascending is None:
273 318 return self._list[-1]
274 319 elif self._ascending:
275 320 return self._asclist[-1]
276 321 else:
277 322 return self._asclist[0]
278 323 return None
279 324
280 325 def __repr__(self):
281 326 d = {None: '', False: '-', True: '+'}[self._ascending]
282 327 s = _formatsetrepr(self._datarepr)
283 328 if not s:
284 329 l = self._list
285 330 # if _list has been built from a set, it might have a different
286 331 # order from one python implementation to another.
287 332 # We fallback to the sorted version for a stable output.
288 333 if self._ascending is not None:
289 334 l = self._asclist
290 335 s = repr(l)
291 336 return '<%s%s %s>' % (type(self).__name__, d, s)
292 337
293 338 class filteredset(abstractsmartset):
294 339 """Duck type for baseset class which iterates lazily over the revisions in
295 340 the subset and contains a function which tests for membership in the
296 341 revset
297 342 """
298 343 def __init__(self, subset, condition=lambda x: True, condrepr=None):
299 344 """
300 345 condition: a function that decide whether a revision in the subset
301 346 belongs to the revset or not.
302 347 condrepr: a tuple of (format, obj, ...), a function or an object that
303 348 provides a printable representation of the given condition.
304 349 """
305 350 self._subset = subset
306 351 self._condition = condition
307 352 self._condrepr = condrepr
308 353
309 354 def __contains__(self, x):
310 355 return x in self._subset and self._condition(x)
311 356
312 357 def __iter__(self):
313 358 return self._iterfilter(self._subset)
314 359
315 360 def _iterfilter(self, it):
316 361 cond = self._condition
317 362 for x in it:
318 363 if cond(x):
319 364 yield x
320 365
321 366 @property
322 367 def fastasc(self):
323 368 it = self._subset.fastasc
324 369 if it is None:
325 370 return None
326 371 return lambda: self._iterfilter(it())
327 372
328 373 @property
329 374 def fastdesc(self):
330 375 it = self._subset.fastdesc
331 376 if it is None:
332 377 return None
333 378 return lambda: self._iterfilter(it())
334 379
335 380 def __nonzero__(self):
336 381 fast = None
337 382 candidates = [self.fastasc if self.isascending() else None,
338 383 self.fastdesc if self.isdescending() else None,
339 384 self.fastasc,
340 385 self.fastdesc]
341 386 for candidate in candidates:
342 387 if candidate is not None:
343 388 fast = candidate
344 389 break
345 390
346 391 if fast is not None:
347 392 it = fast()
348 393 else:
349 394 it = self
350 395
351 396 for r in it:
352 397 return True
353 398 return False
354 399
355 400 def __len__(self):
356 401 # Basic implementation to be changed in future patches.
357 402 # until this gets improved, we use generator expression
358 403 # here, since list comprehensions are free to call __len__ again
359 404 # causing infinite recursion
360 405 l = baseset(r for r in self)
361 406 return len(l)
362 407
363 408 def sort(self, reverse=False):
364 409 self._subset.sort(reverse=reverse)
365 410
366 411 def reverse(self):
367 412 self._subset.reverse()
368 413
369 414 def isascending(self):
370 415 return self._subset.isascending()
371 416
372 417 def isdescending(self):
373 418 return self._subset.isdescending()
374 419
375 420 def istopo(self):
376 421 return self._subset.istopo()
377 422
378 423 def first(self):
379 424 for x in self:
380 425 return x
381 426 return None
382 427
383 428 def last(self):
384 429 it = None
385 430 if self.isascending():
386 431 it = self.fastdesc
387 432 elif self.isdescending():
388 433 it = self.fastasc
389 434 if it is not None:
390 435 for x in it():
391 436 return x
392 437 return None #empty case
393 438 else:
394 439 x = None
395 440 for x in self:
396 441 pass
397 442 return x
398 443
399 444 def __repr__(self):
400 445 xs = [repr(self._subset)]
401 446 s = _formatsetrepr(self._condrepr)
402 447 if s:
403 448 xs.append(s)
404 449 return '<%s %s>' % (type(self).__name__, ', '.join(xs))
405 450
406 451 def _iterordered(ascending, iter1, iter2):
407 452 """produce an ordered iteration from two iterators with the same order
408 453
409 454 The ascending is used to indicated the iteration direction.
410 455 """
411 456 choice = max
412 457 if ascending:
413 458 choice = min
414 459
415 460 val1 = None
416 461 val2 = None
417 462 try:
418 463 # Consume both iterators in an ordered way until one is empty
419 464 while True:
420 465 if val1 is None:
421 466 val1 = next(iter1)
422 467 if val2 is None:
423 468 val2 = next(iter2)
424 469 n = choice(val1, val2)
425 470 yield n
426 471 if val1 == n:
427 472 val1 = None
428 473 if val2 == n:
429 474 val2 = None
430 475 except StopIteration:
431 476 # Flush any remaining values and consume the other one
432 477 it = iter2
433 478 if val1 is not None:
434 479 yield val1
435 480 it = iter1
436 481 elif val2 is not None:
437 482 # might have been equality and both are empty
438 483 yield val2
439 484 for val in it:
440 485 yield val
441 486
442 487 class addset(abstractsmartset):
443 488 """Represent the addition of two sets
444 489
445 490 Wrapper structure for lazily adding two structures without losing much
446 491 performance on the __contains__ method
447 492
448 493 If the ascending attribute is set, that means the two structures are
449 494 ordered in either an ascending or descending way. Therefore, we can add
450 495 them maintaining the order by iterating over both at the same time
451 496
452 497 >>> xs = baseset([0, 3, 2])
453 498 >>> ys = baseset([5, 2, 4])
454 499
455 500 >>> rs = addset(xs, ys)
456 501 >>> bool(rs), 0 in rs, 1 in rs, 5 in rs, rs.first(), rs.last()
457 502 (True, True, False, True, 0, 4)
458 503 >>> rs = addset(xs, baseset([]))
459 504 >>> bool(rs), 0 in rs, 1 in rs, rs.first(), rs.last()
460 505 (True, True, False, 0, 2)
461 506 >>> rs = addset(baseset([]), baseset([]))
462 507 >>> bool(rs), 0 in rs, rs.first(), rs.last()
463 508 (False, False, None, None)
464 509
465 510 iterate unsorted:
466 511 >>> rs = addset(xs, ys)
467 512 >>> # (use generator because pypy could call len())
468 513 >>> list(x for x in rs) # without _genlist
469 514 [0, 3, 2, 5, 4]
470 515 >>> assert not rs._genlist
471 516 >>> len(rs)
472 517 5
473 518 >>> [x for x in rs] # with _genlist
474 519 [0, 3, 2, 5, 4]
475 520 >>> assert rs._genlist
476 521
477 522 iterate ascending:
478 523 >>> rs = addset(xs, ys, ascending=True)
479 524 >>> # (use generator because pypy could call len())
480 525 >>> list(x for x in rs), list(x for x in rs.fastasc()) # without _asclist
481 526 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
482 527 >>> assert not rs._asclist
483 528 >>> len(rs)
484 529 5
485 530 >>> [x for x in rs], [x for x in rs.fastasc()]
486 531 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
487 532 >>> assert rs._asclist
488 533
489 534 iterate descending:
490 535 >>> rs = addset(xs, ys, ascending=False)
491 536 >>> # (use generator because pypy could call len())
492 537 >>> list(x for x in rs), list(x for x in rs.fastdesc()) # without _asclist
493 538 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
494 539 >>> assert not rs._asclist
495 540 >>> len(rs)
496 541 5
497 542 >>> [x for x in rs], [x for x in rs.fastdesc()]
498 543 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
499 544 >>> assert rs._asclist
500 545
501 546 iterate ascending without fastasc:
502 547 >>> rs = addset(xs, generatorset(ys), ascending=True)
503 548 >>> assert rs.fastasc is None
504 549 >>> [x for x in rs]
505 550 [0, 2, 3, 4, 5]
506 551
507 552 iterate descending without fastdesc:
508 553 >>> rs = addset(generatorset(xs), ys, ascending=False)
509 554 >>> assert rs.fastdesc is None
510 555 >>> [x for x in rs]
511 556 [5, 4, 3, 2, 0]
512 557 """
513 558 def __init__(self, revs1, revs2, ascending=None):
514 559 self._r1 = revs1
515 560 self._r2 = revs2
516 561 self._iter = None
517 562 self._ascending = ascending
518 563 self._genlist = None
519 564 self._asclist = None
520 565
521 566 def __len__(self):
522 567 return len(self._list)
523 568
524 569 def __nonzero__(self):
525 570 return bool(self._r1) or bool(self._r2)
526 571
527 572 @util.propertycache
528 573 def _list(self):
529 574 if not self._genlist:
530 575 self._genlist = baseset(iter(self))
531 576 return self._genlist
532 577
533 578 def __iter__(self):
534 579 """Iterate over both collections without repeating elements
535 580
536 581 If the ascending attribute is not set, iterate over the first one and
537 582 then over the second one checking for membership on the first one so we
538 583 dont yield any duplicates.
539 584
540 585 If the ascending attribute is set, iterate over both collections at the
541 586 same time, yielding only one value at a time in the given order.
542 587 """
543 588 if self._ascending is None:
544 589 if self._genlist:
545 590 return iter(self._genlist)
546 591 def arbitraryordergen():
547 592 for r in self._r1:
548 593 yield r
549 594 inr1 = self._r1.__contains__
550 595 for r in self._r2:
551 596 if not inr1(r):
552 597 yield r
553 598 return arbitraryordergen()
554 599 # try to use our own fast iterator if it exists
555 600 self._trysetasclist()
556 601 if self._ascending:
557 602 attr = 'fastasc'
558 603 else:
559 604 attr = 'fastdesc'
560 605 it = getattr(self, attr)
561 606 if it is not None:
562 607 return it()
563 608 # maybe half of the component supports fast
564 609 # get iterator for _r1
565 610 iter1 = getattr(self._r1, attr)
566 611 if iter1 is None:
567 612 # let's avoid side effect (not sure it matters)
568 613 iter1 = iter(sorted(self._r1, reverse=not self._ascending))
569 614 else:
570 615 iter1 = iter1()
571 616 # get iterator for _r2
572 617 iter2 = getattr(self._r2, attr)
573 618 if iter2 is None:
574 619 # let's avoid side effect (not sure it matters)
575 620 iter2 = iter(sorted(self._r2, reverse=not self._ascending))
576 621 else:
577 622 iter2 = iter2()
578 623 return _iterordered(self._ascending, iter1, iter2)
579 624
580 625 def _trysetasclist(self):
581 626 """populate the _asclist attribute if possible and necessary"""
582 627 if self._genlist is not None and self._asclist is None:
583 628 self._asclist = sorted(self._genlist)
584 629
585 630 @property
586 631 def fastasc(self):
587 632 self._trysetasclist()
588 633 if self._asclist is not None:
589 634 return self._asclist.__iter__
590 635 iter1 = self._r1.fastasc
591 636 iter2 = self._r2.fastasc
592 637 if None in (iter1, iter2):
593 638 return None
594 639 return lambda: _iterordered(True, iter1(), iter2())
595 640
596 641 @property
597 642 def fastdesc(self):
598 643 self._trysetasclist()
599 644 if self._asclist is not None:
600 645 return self._asclist.__reversed__
601 646 iter1 = self._r1.fastdesc
602 647 iter2 = self._r2.fastdesc
603 648 if None in (iter1, iter2):
604 649 return None
605 650 return lambda: _iterordered(False, iter1(), iter2())
606 651
607 652 def __contains__(self, x):
608 653 return x in self._r1 or x in self._r2
609 654
610 655 def sort(self, reverse=False):
611 656 """Sort the added set
612 657
613 658 For this we use the cached list with all the generated values and if we
614 659 know they are ascending or descending we can sort them in a smart way.
615 660 """
616 661 self._ascending = not reverse
617 662
618 663 def isascending(self):
619 664 return self._ascending is not None and self._ascending
620 665
621 666 def isdescending(self):
622 667 return self._ascending is not None and not self._ascending
623 668
624 669 def istopo(self):
625 670 # not worth the trouble asserting if the two sets combined are still
626 671 # in topographical order. Use the sort() predicate to explicitly sort
627 672 # again instead.
628 673 return False
629 674
630 675 def reverse(self):
631 676 if self._ascending is None:
632 677 self._list.reverse()
633 678 else:
634 679 self._ascending = not self._ascending
635 680
636 681 def first(self):
637 682 for x in self:
638 683 return x
639 684 return None
640 685
641 686 def last(self):
642 687 self.reverse()
643 688 val = self.first()
644 689 self.reverse()
645 690 return val
646 691
647 692 def __repr__(self):
648 693 d = {None: '', False: '-', True: '+'}[self._ascending]
649 694 return '<%s%s %r, %r>' % (type(self).__name__, d, self._r1, self._r2)
650 695
651 696 class generatorset(abstractsmartset):
652 697 """Wrap a generator for lazy iteration
653 698
654 699 Wrapper structure for generators that provides lazy membership and can
655 700 be iterated more than once.
656 701 When asked for membership it generates values until either it finds the
657 702 requested one or has gone through all the elements in the generator
658 703 """
659 704 def __init__(self, gen, iterasc=None):
660 705 """
661 706 gen: a generator producing the values for the generatorset.
662 707 """
663 708 self._gen = gen
664 709 self._asclist = None
665 710 self._cache = {}
666 711 self._genlist = []
667 712 self._finished = False
668 713 self._ascending = True
669 714 if iterasc is not None:
670 715 if iterasc:
671 716 self.fastasc = self._iterator
672 717 self.__contains__ = self._asccontains
673 718 else:
674 719 self.fastdesc = self._iterator
675 720 self.__contains__ = self._desccontains
676 721
677 722 def __nonzero__(self):
678 723 # Do not use 'for r in self' because it will enforce the iteration
679 724 # order (default ascending), possibly unrolling a whole descending
680 725 # iterator.
681 726 if self._genlist:
682 727 return True
683 728 for r in self._consumegen():
684 729 return True
685 730 return False
686 731
687 732 def __contains__(self, x):
688 733 if x in self._cache:
689 734 return self._cache[x]
690 735
691 736 # Use new values only, as existing values would be cached.
692 737 for l in self._consumegen():
693 738 if l == x:
694 739 return True
695 740
696 741 self._cache[x] = False
697 742 return False
698 743
699 744 def _asccontains(self, x):
700 745 """version of contains optimised for ascending generator"""
701 746 if x in self._cache:
702 747 return self._cache[x]
703 748
704 749 # Use new values only, as existing values would be cached.
705 750 for l in self._consumegen():
706 751 if l == x:
707 752 return True
708 753 if l > x:
709 754 break
710 755
711 756 self._cache[x] = False
712 757 return False
713 758
714 759 def _desccontains(self, x):
715 760 """version of contains optimised for descending generator"""
716 761 if x in self._cache:
717 762 return self._cache[x]
718 763
719 764 # Use new values only, as existing values would be cached.
720 765 for l in self._consumegen():
721 766 if l == x:
722 767 return True
723 768 if l < x:
724 769 break
725 770
726 771 self._cache[x] = False
727 772 return False
728 773
729 774 def __iter__(self):
730 775 if self._ascending:
731 776 it = self.fastasc
732 777 else:
733 778 it = self.fastdesc
734 779 if it is not None:
735 780 return it()
736 781 # we need to consume the iterator
737 782 for x in self._consumegen():
738 783 pass
739 784 # recall the same code
740 785 return iter(self)
741 786
742 787 def _iterator(self):
743 788 if self._finished:
744 789 return iter(self._genlist)
745 790
746 791 # We have to use this complex iteration strategy to allow multiple
747 792 # iterations at the same time. We need to be able to catch revision
748 793 # removed from _consumegen and added to genlist in another instance.
749 794 #
750 795 # Getting rid of it would provide an about 15% speed up on this
751 796 # iteration.
752 797 genlist = self._genlist
753 798 nextrev = self._consumegen().next
754 799 _len = len # cache global lookup
755 800 def gen():
756 801 i = 0
757 802 while True:
758 803 if i < _len(genlist):
759 804 yield genlist[i]
760 805 else:
761 806 yield nextrev()
762 807 i += 1
763 808 return gen()
764 809
765 810 def _consumegen(self):
766 811 cache = self._cache
767 812 genlist = self._genlist.append
768 813 for item in self._gen:
769 814 cache[item] = True
770 815 genlist(item)
771 816 yield item
772 817 if not self._finished:
773 818 self._finished = True
774 819 asc = self._genlist[:]
775 820 asc.sort()
776 821 self._asclist = asc
777 822 self.fastasc = asc.__iter__
778 823 self.fastdesc = asc.__reversed__
779 824
780 825 def __len__(self):
781 826 for x in self._consumegen():
782 827 pass
783 828 return len(self._genlist)
784 829
785 830 def sort(self, reverse=False):
786 831 self._ascending = not reverse
787 832
788 833 def reverse(self):
789 834 self._ascending = not self._ascending
790 835
791 836 def isascending(self):
792 837 return self._ascending
793 838
794 839 def isdescending(self):
795 840 return not self._ascending
796 841
797 842 def istopo(self):
798 843 # not worth the trouble asserting if the two sets combined are still
799 844 # in topographical order. Use the sort() predicate to explicitly sort
800 845 # again instead.
801 846 return False
802 847
803 848 def first(self):
804 849 if self._ascending:
805 850 it = self.fastasc
806 851 else:
807 852 it = self.fastdesc
808 853 if it is None:
809 854 # we need to consume all and try again
810 855 for x in self._consumegen():
811 856 pass
812 857 return self.first()
813 858 return next(it(), None)
814 859
815 860 def last(self):
816 861 if self._ascending:
817 862 it = self.fastdesc
818 863 else:
819 864 it = self.fastasc
820 865 if it is None:
821 866 # we need to consume all and try again
822 867 for x in self._consumegen():
823 868 pass
824 869 return self.first()
825 870 return next(it(), None)
826 871
827 872 def __repr__(self):
828 873 d = {False: '-', True: '+'}[self._ascending]
829 874 return '<%s%s>' % (type(self).__name__, d)
830 875
831 876 class spanset(abstractsmartset):
832 877 """Duck type for baseset class which represents a range of revisions and
833 878 can work lazily and without having all the range in memory
834 879
835 880 Note that spanset(x, y) behave almost like xrange(x, y) except for two
836 881 notable points:
837 882 - when x < y it will be automatically descending,
838 883 - revision filtered with this repoview will be skipped.
839 884
840 885 """
841 886 def __init__(self, repo, start=0, end=None):
842 887 """
843 888 start: first revision included the set
844 889 (default to 0)
845 890 end: first revision excluded (last+1)
846 891 (default to len(repo)
847 892
848 893 Spanset will be descending if `end` < `start`.
849 894 """
850 895 if end is None:
851 896 end = len(repo)
852 897 self._ascending = start <= end
853 898 if not self._ascending:
854 899 start, end = end + 1, start +1
855 900 self._start = start
856 901 self._end = end
857 902 self._hiddenrevs = repo.changelog.filteredrevs
858 903
859 904 def sort(self, reverse=False):
860 905 self._ascending = not reverse
861 906
862 907 def reverse(self):
863 908 self._ascending = not self._ascending
864 909
865 910 def istopo(self):
866 911 # not worth the trouble asserting if the two sets combined are still
867 912 # in topographical order. Use the sort() predicate to explicitly sort
868 913 # again instead.
869 914 return False
870 915
871 916 def _iterfilter(self, iterrange):
872 917 s = self._hiddenrevs
873 918 for r in iterrange:
874 919 if r not in s:
875 920 yield r
876 921
877 922 def __iter__(self):
878 923 if self._ascending:
879 924 return self.fastasc()
880 925 else:
881 926 return self.fastdesc()
882 927
883 928 def fastasc(self):
884 929 iterrange = xrange(self._start, self._end)
885 930 if self._hiddenrevs:
886 931 return self._iterfilter(iterrange)
887 932 return iter(iterrange)
888 933
889 934 def fastdesc(self):
890 935 iterrange = xrange(self._end - 1, self._start - 1, -1)
891 936 if self._hiddenrevs:
892 937 return self._iterfilter(iterrange)
893 938 return iter(iterrange)
894 939
895 940 def __contains__(self, rev):
896 941 hidden = self._hiddenrevs
897 942 return ((self._start <= rev < self._end)
898 943 and not (hidden and rev in hidden))
899 944
900 945 def __nonzero__(self):
901 946 for r in self:
902 947 return True
903 948 return False
904 949
905 950 def __len__(self):
906 951 if not self._hiddenrevs:
907 952 return abs(self._end - self._start)
908 953 else:
909 954 count = 0
910 955 start = self._start
911 956 end = self._end
912 957 for rev in self._hiddenrevs:
913 958 if (end < rev <= start) or (start <= rev < end):
914 959 count += 1
915 960 return abs(self._end - self._start) - count
916 961
917 962 def isascending(self):
918 963 return self._ascending
919 964
920 965 def isdescending(self):
921 966 return not self._ascending
922 967
923 968 def first(self):
924 969 if self._ascending:
925 970 it = self.fastasc
926 971 else:
927 972 it = self.fastdesc
928 973 for x in it():
929 974 return x
930 975 return None
931 976
932 977 def last(self):
933 978 if self._ascending:
934 979 it = self.fastdesc
935 980 else:
936 981 it = self.fastasc
937 982 for x in it():
938 983 return x
939 984 return None
940 985
941 986 def __repr__(self):
942 987 d = {False: '-', True: '+'}[self._ascending]
943 988 return '<%s%s %d:%d>' % (type(self).__name__, d,
944 989 self._start, self._end - 1)
945 990
946 991 class fullreposet(spanset):
947 992 """a set containing all revisions in the repo
948 993
949 994 This class exists to host special optimization and magic to handle virtual
950 995 revisions such as "null".
951 996 """
952 997
953 998 def __init__(self, repo):
954 999 super(fullreposet, self).__init__(repo)
955 1000
956 1001 def __and__(self, other):
957 1002 """As self contains the whole repo, all of the other set should also be
958 1003 in self. Therefore `self & other = other`.
959 1004
960 1005 This boldly assumes the other contains valid revs only.
961 1006 """
962 1007 # other not a smartset, make is so
963 1008 if not util.safehasattr(other, 'isascending'):
964 1009 # filter out hidden revision
965 1010 # (this boldly assumes all smartset are pure)
966 1011 #
967 1012 # `other` was used with "&", let's assume this is a set like
968 1013 # object.
969 1014 other = baseset(other - self._hiddenrevs)
970 1015
971 1016 other.sort(reverse=self.isdescending())
972 1017 return other
973 1018
974 1019 def prettyformat(revs):
975 1020 lines = []
976 1021 rs = repr(revs)
977 1022 p = 0
978 1023 while p < len(rs):
979 1024 q = rs.find('<', p + 1)
980 1025 if q < 0:
981 1026 q = len(rs)
982 1027 l = rs.count('<', 0, p) - rs.count('>', 0, p)
983 1028 assert l >= 0
984 1029 lines.append((l, rs[p:q].rstrip()))
985 1030 p = q
986 1031 return '\n'.join(' ' * l + s for l, s in lines)
General Comments 0
You need to be logged in to leave comments. Login now