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