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