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