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