comparison mercurial/revset.py @ 30913:1be65deb3d54

smartset: move set classes and related functions from revset module (API) These classes are pretty large and independent from revset computation. 2961 mercurial/revset.py 973 mercurial/smartset.py 3934 total revset.prettyformatset() is renamed to smartset.prettyformat(). Smartset classes are aliased since they are quite common in revset.py.
author Yuya Nishihara <yuya@tcha.org>
date Sun, 16 Oct 2016 17:28:51 +0900
parents 41e31a6f5296
children 11c253997b0e
comparison
equal deleted inserted replaced
30912:b6c051cd1231 30913:1be65deb3d54
24 pathutil, 24 pathutil,
25 phases, 25 phases,
26 pycompat, 26 pycompat,
27 registrar, 27 registrar,
28 repoview, 28 repoview,
29 smartset,
29 util, 30 util,
30 ) 31 )
32
33 baseset = smartset.baseset
34 generatorset = smartset.generatorset
35 spanset = smartset.spanset
36 fullreposet = smartset.fullreposet
31 37
32 def _revancestors(repo, revs, followfirst): 38 def _revancestors(repo, revs, followfirst):
33 """Like revlog.ancestors(), but supports followfirst.""" 39 """Like revlog.ancestors(), but supports followfirst."""
34 if followfirst: 40 if followfirst:
35 cut = 1 41 cut = 1
2938 funcs |= funcsused(s) 2944 funcs |= funcsused(s)
2939 if tree[0] == 'func': 2945 if tree[0] == 'func':
2940 funcs.add(tree[1][1]) 2946 funcs.add(tree[1][1])
2941 return funcs 2947 return funcs
2942 2948
2943 def _formatsetrepr(r):
2944 """Format an optional printable representation of a set
2945
2946 ======== =================================
2947 type(r) example
2948 ======== =================================
2949 tuple ('<not %r>', other)
2950 str '<branch closed>'
2951 callable lambda: '<branch %r>' % sorted(b)
2952 object other
2953 ======== =================================
2954 """
2955 if r is None:
2956 return ''
2957 elif isinstance(r, tuple):
2958 return r[0] % r[1:]
2959 elif isinstance(r, str):
2960 return r
2961 elif callable(r):
2962 return r()
2963 else:
2964 return repr(r)
2965
2966 class abstractsmartset(object):
2967
2968 def __nonzero__(self):
2969 """True if the smartset is not empty"""
2970 raise NotImplementedError()
2971
2972 def __contains__(self, rev):
2973 """provide fast membership testing"""
2974 raise NotImplementedError()
2975
2976 def __iter__(self):
2977 """iterate the set in the order it is supposed to be iterated"""
2978 raise NotImplementedError()
2979
2980 # Attributes containing a function to perform a fast iteration in a given
2981 # direction. A smartset can have none, one, or both defined.
2982 #
2983 # Default value is None instead of a function returning None to avoid
2984 # initializing an iterator just for testing if a fast method exists.
2985 fastasc = None
2986 fastdesc = None
2987
2988 def isascending(self):
2989 """True if the set will iterate in ascending order"""
2990 raise NotImplementedError()
2991
2992 def isdescending(self):
2993 """True if the set will iterate in descending order"""
2994 raise NotImplementedError()
2995
2996 def istopo(self):
2997 """True if the set will iterate in topographical order"""
2998 raise NotImplementedError()
2999
3000 def min(self):
3001 """return the minimum element in the set"""
3002 if self.fastasc is None:
3003 v = min(self)
3004 else:
3005 for v in self.fastasc():
3006 break
3007 else:
3008 raise ValueError('arg is an empty sequence')
3009 self.min = lambda: v
3010 return v
3011
3012 def max(self):
3013 """return the maximum element in the set"""
3014 if self.fastdesc is None:
3015 return max(self)
3016 else:
3017 for v in self.fastdesc():
3018 break
3019 else:
3020 raise ValueError('arg is an empty sequence')
3021 self.max = lambda: v
3022 return v
3023
3024 def first(self):
3025 """return the first element in the set (user iteration perspective)
3026
3027 Return None if the set is empty"""
3028 raise NotImplementedError()
3029
3030 def last(self):
3031 """return the last element in the set (user iteration perspective)
3032
3033 Return None if the set is empty"""
3034 raise NotImplementedError()
3035
3036 def __len__(self):
3037 """return the length of the smartsets
3038
3039 This can be expensive on smartset that could be lazy otherwise."""
3040 raise NotImplementedError()
3041
3042 def reverse(self):
3043 """reverse the expected iteration order"""
3044 raise NotImplementedError()
3045
3046 def sort(self, reverse=True):
3047 """get the set to iterate in an ascending or descending order"""
3048 raise NotImplementedError()
3049
3050 def __and__(self, other):
3051 """Returns a new object with the intersection of the two collections.
3052
3053 This is part of the mandatory API for smartset."""
3054 if isinstance(other, fullreposet):
3055 return self
3056 return self.filter(other.__contains__, condrepr=other, cache=False)
3057
3058 def __add__(self, other):
3059 """Returns a new object with the union of the two collections.
3060
3061 This is part of the mandatory API for smartset."""
3062 return addset(self, other)
3063
3064 def __sub__(self, other):
3065 """Returns a new object with the substraction of the two collections.
3066
3067 This is part of the mandatory API for smartset."""
3068 c = other.__contains__
3069 return self.filter(lambda r: not c(r), condrepr=('<not %r>', other),
3070 cache=False)
3071
3072 def filter(self, condition, condrepr=None, cache=True):
3073 """Returns this smartset filtered by condition as a new smartset.
3074
3075 `condition` is a callable which takes a revision number and returns a
3076 boolean. Optional `condrepr` provides a printable representation of
3077 the given `condition`.
3078
3079 This is part of the mandatory API for smartset."""
3080 # builtin cannot be cached. but do not needs to
3081 if cache and util.safehasattr(condition, 'func_code'):
3082 condition = util.cachefunc(condition)
3083 return filteredset(self, condition, condrepr)
3084
3085 class baseset(abstractsmartset):
3086 """Basic data structure that represents a revset and contains the basic
3087 operation that it should be able to perform.
3088
3089 Every method in this class should be implemented by any smartset class.
3090 """
3091 def __init__(self, data=(), datarepr=None, istopo=False):
3092 """
3093 datarepr: a tuple of (format, obj, ...), a function or an object that
3094 provides a printable representation of the given data.
3095 """
3096 self._ascending = None
3097 self._istopo = istopo
3098 if not isinstance(data, list):
3099 if isinstance(data, set):
3100 self._set = data
3101 # set has no order we pick one for stability purpose
3102 self._ascending = True
3103 data = list(data)
3104 self._list = data
3105 self._datarepr = datarepr
3106
3107 @util.propertycache
3108 def _set(self):
3109 return set(self._list)
3110
3111 @util.propertycache
3112 def _asclist(self):
3113 asclist = self._list[:]
3114 asclist.sort()
3115 return asclist
3116
3117 def __iter__(self):
3118 if self._ascending is None:
3119 return iter(self._list)
3120 elif self._ascending:
3121 return iter(self._asclist)
3122 else:
3123 return reversed(self._asclist)
3124
3125 def fastasc(self):
3126 return iter(self._asclist)
3127
3128 def fastdesc(self):
3129 return reversed(self._asclist)
3130
3131 @util.propertycache
3132 def __contains__(self):
3133 return self._set.__contains__
3134
3135 def __nonzero__(self):
3136 return bool(self._list)
3137
3138 def sort(self, reverse=False):
3139 self._ascending = not bool(reverse)
3140 self._istopo = False
3141
3142 def reverse(self):
3143 if self._ascending is None:
3144 self._list.reverse()
3145 else:
3146 self._ascending = not self._ascending
3147 self._istopo = False
3148
3149 def __len__(self):
3150 return len(self._list)
3151
3152 def isascending(self):
3153 """Returns True if the collection is ascending order, False if not.
3154
3155 This is part of the mandatory API for smartset."""
3156 if len(self) <= 1:
3157 return True
3158 return self._ascending is not None and self._ascending
3159
3160 def isdescending(self):
3161 """Returns True if the collection is descending order, False if not.
3162
3163 This is part of the mandatory API for smartset."""
3164 if len(self) <= 1:
3165 return True
3166 return self._ascending is not None and not self._ascending
3167
3168 def istopo(self):
3169 """Is the collection is in topographical order or not.
3170
3171 This is part of the mandatory API for smartset."""
3172 if len(self) <= 1:
3173 return True
3174 return self._istopo
3175
3176 def first(self):
3177 if self:
3178 if self._ascending is None:
3179 return self._list[0]
3180 elif self._ascending:
3181 return self._asclist[0]
3182 else:
3183 return self._asclist[-1]
3184 return None
3185
3186 def last(self):
3187 if self:
3188 if self._ascending is None:
3189 return self._list[-1]
3190 elif self._ascending:
3191 return self._asclist[-1]
3192 else:
3193 return self._asclist[0]
3194 return None
3195
3196 def __repr__(self):
3197 d = {None: '', False: '-', True: '+'}[self._ascending]
3198 s = _formatsetrepr(self._datarepr)
3199 if not s:
3200 l = self._list
3201 # if _list has been built from a set, it might have a different
3202 # order from one python implementation to another.
3203 # We fallback to the sorted version for a stable output.
3204 if self._ascending is not None:
3205 l = self._asclist
3206 s = repr(l)
3207 return '<%s%s %s>' % (type(self).__name__, d, s)
3208
3209 class filteredset(abstractsmartset):
3210 """Duck type for baseset class which iterates lazily over the revisions in
3211 the subset and contains a function which tests for membership in the
3212 revset
3213 """
3214 def __init__(self, subset, condition=lambda x: True, condrepr=None):
3215 """
3216 condition: a function that decide whether a revision in the subset
3217 belongs to the revset or not.
3218 condrepr: a tuple of (format, obj, ...), a function or an object that
3219 provides a printable representation of the given condition.
3220 """
3221 self._subset = subset
3222 self._condition = condition
3223 self._condrepr = condrepr
3224
3225 def __contains__(self, x):
3226 return x in self._subset and self._condition(x)
3227
3228 def __iter__(self):
3229 return self._iterfilter(self._subset)
3230
3231 def _iterfilter(self, it):
3232 cond = self._condition
3233 for x in it:
3234 if cond(x):
3235 yield x
3236
3237 @property
3238 def fastasc(self):
3239 it = self._subset.fastasc
3240 if it is None:
3241 return None
3242 return lambda: self._iterfilter(it())
3243
3244 @property
3245 def fastdesc(self):
3246 it = self._subset.fastdesc
3247 if it is None:
3248 return None
3249 return lambda: self._iterfilter(it())
3250
3251 def __nonzero__(self):
3252 fast = None
3253 candidates = [self.fastasc if self.isascending() else None,
3254 self.fastdesc if self.isdescending() else None,
3255 self.fastasc,
3256 self.fastdesc]
3257 for candidate in candidates:
3258 if candidate is not None:
3259 fast = candidate
3260 break
3261
3262 if fast is not None:
3263 it = fast()
3264 else:
3265 it = self
3266
3267 for r in it:
3268 return True
3269 return False
3270
3271 def __len__(self):
3272 # Basic implementation to be changed in future patches.
3273 # until this gets improved, we use generator expression
3274 # here, since list comprehensions are free to call __len__ again
3275 # causing infinite recursion
3276 l = baseset(r for r in self)
3277 return len(l)
3278
3279 def sort(self, reverse=False):
3280 self._subset.sort(reverse=reverse)
3281
3282 def reverse(self):
3283 self._subset.reverse()
3284
3285 def isascending(self):
3286 return self._subset.isascending()
3287
3288 def isdescending(self):
3289 return self._subset.isdescending()
3290
3291 def istopo(self):
3292 return self._subset.istopo()
3293
3294 def first(self):
3295 for x in self:
3296 return x
3297 return None
3298
3299 def last(self):
3300 it = None
3301 if self.isascending():
3302 it = self.fastdesc
3303 elif self.isdescending():
3304 it = self.fastasc
3305 if it is not None:
3306 for x in it():
3307 return x
3308 return None #empty case
3309 else:
3310 x = None
3311 for x in self:
3312 pass
3313 return x
3314
3315 def __repr__(self):
3316 xs = [repr(self._subset)]
3317 s = _formatsetrepr(self._condrepr)
3318 if s:
3319 xs.append(s)
3320 return '<%s %s>' % (type(self).__name__, ', '.join(xs))
3321
3322 def _iterordered(ascending, iter1, iter2):
3323 """produce an ordered iteration from two iterators with the same order
3324
3325 The ascending is used to indicated the iteration direction.
3326 """
3327 choice = max
3328 if ascending:
3329 choice = min
3330
3331 val1 = None
3332 val2 = None
3333 try:
3334 # Consume both iterators in an ordered way until one is empty
3335 while True:
3336 if val1 is None:
3337 val1 = next(iter1)
3338 if val2 is None:
3339 val2 = next(iter2)
3340 n = choice(val1, val2)
3341 yield n
3342 if val1 == n:
3343 val1 = None
3344 if val2 == n:
3345 val2 = None
3346 except StopIteration:
3347 # Flush any remaining values and consume the other one
3348 it = iter2
3349 if val1 is not None:
3350 yield val1
3351 it = iter1
3352 elif val2 is not None:
3353 # might have been equality and both are empty
3354 yield val2
3355 for val in it:
3356 yield val
3357
3358 class addset(abstractsmartset):
3359 """Represent the addition of two sets
3360
3361 Wrapper structure for lazily adding two structures without losing much
3362 performance on the __contains__ method
3363
3364 If the ascending attribute is set, that means the two structures are
3365 ordered in either an ascending or descending way. Therefore, we can add
3366 them maintaining the order by iterating over both at the same time
3367
3368 >>> xs = baseset([0, 3, 2])
3369 >>> ys = baseset([5, 2, 4])
3370
3371 >>> rs = addset(xs, ys)
3372 >>> bool(rs), 0 in rs, 1 in rs, 5 in rs, rs.first(), rs.last()
3373 (True, True, False, True, 0, 4)
3374 >>> rs = addset(xs, baseset([]))
3375 >>> bool(rs), 0 in rs, 1 in rs, rs.first(), rs.last()
3376 (True, True, False, 0, 2)
3377 >>> rs = addset(baseset([]), baseset([]))
3378 >>> bool(rs), 0 in rs, rs.first(), rs.last()
3379 (False, False, None, None)
3380
3381 iterate unsorted:
3382 >>> rs = addset(xs, ys)
3383 >>> # (use generator because pypy could call len())
3384 >>> list(x for x in rs) # without _genlist
3385 [0, 3, 2, 5, 4]
3386 >>> assert not rs._genlist
3387 >>> len(rs)
3388 5
3389 >>> [x for x in rs] # with _genlist
3390 [0, 3, 2, 5, 4]
3391 >>> assert rs._genlist
3392
3393 iterate ascending:
3394 >>> rs = addset(xs, ys, ascending=True)
3395 >>> # (use generator because pypy could call len())
3396 >>> list(x for x in rs), list(x for x in rs.fastasc()) # without _asclist
3397 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
3398 >>> assert not rs._asclist
3399 >>> len(rs)
3400 5
3401 >>> [x for x in rs], [x for x in rs.fastasc()]
3402 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
3403 >>> assert rs._asclist
3404
3405 iterate descending:
3406 >>> rs = addset(xs, ys, ascending=False)
3407 >>> # (use generator because pypy could call len())
3408 >>> list(x for x in rs), list(x for x in rs.fastdesc()) # without _asclist
3409 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
3410 >>> assert not rs._asclist
3411 >>> len(rs)
3412 5
3413 >>> [x for x in rs], [x for x in rs.fastdesc()]
3414 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
3415 >>> assert rs._asclist
3416
3417 iterate ascending without fastasc:
3418 >>> rs = addset(xs, generatorset(ys), ascending=True)
3419 >>> assert rs.fastasc is None
3420 >>> [x for x in rs]
3421 [0, 2, 3, 4, 5]
3422
3423 iterate descending without fastdesc:
3424 >>> rs = addset(generatorset(xs), ys, ascending=False)
3425 >>> assert rs.fastdesc is None
3426 >>> [x for x in rs]
3427 [5, 4, 3, 2, 0]
3428 """
3429 def __init__(self, revs1, revs2, ascending=None):
3430 self._r1 = revs1
3431 self._r2 = revs2
3432 self._iter = None
3433 self._ascending = ascending
3434 self._genlist = None
3435 self._asclist = None
3436
3437 def __len__(self):
3438 return len(self._list)
3439
3440 def __nonzero__(self):
3441 return bool(self._r1) or bool(self._r2)
3442
3443 @util.propertycache
3444 def _list(self):
3445 if not self._genlist:
3446 self._genlist = baseset(iter(self))
3447 return self._genlist
3448
3449 def __iter__(self):
3450 """Iterate over both collections without repeating elements
3451
3452 If the ascending attribute is not set, iterate over the first one and
3453 then over the second one checking for membership on the first one so we
3454 dont yield any duplicates.
3455
3456 If the ascending attribute is set, iterate over both collections at the
3457 same time, yielding only one value at a time in the given order.
3458 """
3459 if self._ascending is None:
3460 if self._genlist:
3461 return iter(self._genlist)
3462 def arbitraryordergen():
3463 for r in self._r1:
3464 yield r
3465 inr1 = self._r1.__contains__
3466 for r in self._r2:
3467 if not inr1(r):
3468 yield r
3469 return arbitraryordergen()
3470 # try to use our own fast iterator if it exists
3471 self._trysetasclist()
3472 if self._ascending:
3473 attr = 'fastasc'
3474 else:
3475 attr = 'fastdesc'
3476 it = getattr(self, attr)
3477 if it is not None:
3478 return it()
3479 # maybe half of the component supports fast
3480 # get iterator for _r1
3481 iter1 = getattr(self._r1, attr)
3482 if iter1 is None:
3483 # let's avoid side effect (not sure it matters)
3484 iter1 = iter(sorted(self._r1, reverse=not self._ascending))
3485 else:
3486 iter1 = iter1()
3487 # get iterator for _r2
3488 iter2 = getattr(self._r2, attr)
3489 if iter2 is None:
3490 # let's avoid side effect (not sure it matters)
3491 iter2 = iter(sorted(self._r2, reverse=not self._ascending))
3492 else:
3493 iter2 = iter2()
3494 return _iterordered(self._ascending, iter1, iter2)
3495
3496 def _trysetasclist(self):
3497 """populate the _asclist attribute if possible and necessary"""
3498 if self._genlist is not None and self._asclist is None:
3499 self._asclist = sorted(self._genlist)
3500
3501 @property
3502 def fastasc(self):
3503 self._trysetasclist()
3504 if self._asclist is not None:
3505 return self._asclist.__iter__
3506 iter1 = self._r1.fastasc
3507 iter2 = self._r2.fastasc
3508 if None in (iter1, iter2):
3509 return None
3510 return lambda: _iterordered(True, iter1(), iter2())
3511
3512 @property
3513 def fastdesc(self):
3514 self._trysetasclist()
3515 if self._asclist is not None:
3516 return self._asclist.__reversed__
3517 iter1 = self._r1.fastdesc
3518 iter2 = self._r2.fastdesc
3519 if None in (iter1, iter2):
3520 return None
3521 return lambda: _iterordered(False, iter1(), iter2())
3522
3523 def __contains__(self, x):
3524 return x in self._r1 or x in self._r2
3525
3526 def sort(self, reverse=False):
3527 """Sort the added set
3528
3529 For this we use the cached list with all the generated values and if we
3530 know they are ascending or descending we can sort them in a smart way.
3531 """
3532 self._ascending = not reverse
3533
3534 def isascending(self):
3535 return self._ascending is not None and self._ascending
3536
3537 def isdescending(self):
3538 return self._ascending is not None and not self._ascending
3539
3540 def istopo(self):
3541 # not worth the trouble asserting if the two sets combined are still
3542 # in topographical order. Use the sort() predicate to explicitly sort
3543 # again instead.
3544 return False
3545
3546 def reverse(self):
3547 if self._ascending is None:
3548 self._list.reverse()
3549 else:
3550 self._ascending = not self._ascending
3551
3552 def first(self):
3553 for x in self:
3554 return x
3555 return None
3556
3557 def last(self):
3558 self.reverse()
3559 val = self.first()
3560 self.reverse()
3561 return val
3562
3563 def __repr__(self):
3564 d = {None: '', False: '-', True: '+'}[self._ascending]
3565 return '<%s%s %r, %r>' % (type(self).__name__, d, self._r1, self._r2)
3566
3567 class generatorset(abstractsmartset):
3568 """Wrap a generator for lazy iteration
3569
3570 Wrapper structure for generators that provides lazy membership and can
3571 be iterated more than once.
3572 When asked for membership it generates values until either it finds the
3573 requested one or has gone through all the elements in the generator
3574 """
3575 def __init__(self, gen, iterasc=None):
3576 """
3577 gen: a generator producing the values for the generatorset.
3578 """
3579 self._gen = gen
3580 self._asclist = None
3581 self._cache = {}
3582 self._genlist = []
3583 self._finished = False
3584 self._ascending = True
3585 if iterasc is not None:
3586 if iterasc:
3587 self.fastasc = self._iterator
3588 self.__contains__ = self._asccontains
3589 else:
3590 self.fastdesc = self._iterator
3591 self.__contains__ = self._desccontains
3592
3593 def __nonzero__(self):
3594 # Do not use 'for r in self' because it will enforce the iteration
3595 # order (default ascending), possibly unrolling a whole descending
3596 # iterator.
3597 if self._genlist:
3598 return True
3599 for r in self._consumegen():
3600 return True
3601 return False
3602
3603 def __contains__(self, x):
3604 if x in self._cache:
3605 return self._cache[x]
3606
3607 # Use new values only, as existing values would be cached.
3608 for l in self._consumegen():
3609 if l == x:
3610 return True
3611
3612 self._cache[x] = False
3613 return False
3614
3615 def _asccontains(self, x):
3616 """version of contains optimised for ascending generator"""
3617 if x in self._cache:
3618 return self._cache[x]
3619
3620 # Use new values only, as existing values would be cached.
3621 for l in self._consumegen():
3622 if l == x:
3623 return True
3624 if l > x:
3625 break
3626
3627 self._cache[x] = False
3628 return False
3629
3630 def _desccontains(self, x):
3631 """version of contains optimised for descending generator"""
3632 if x in self._cache:
3633 return self._cache[x]
3634
3635 # Use new values only, as existing values would be cached.
3636 for l in self._consumegen():
3637 if l == x:
3638 return True
3639 if l < x:
3640 break
3641
3642 self._cache[x] = False
3643 return False
3644
3645 def __iter__(self):
3646 if self._ascending:
3647 it = self.fastasc
3648 else:
3649 it = self.fastdesc
3650 if it is not None:
3651 return it()
3652 # we need to consume the iterator
3653 for x in self._consumegen():
3654 pass
3655 # recall the same code
3656 return iter(self)
3657
3658 def _iterator(self):
3659 if self._finished:
3660 return iter(self._genlist)
3661
3662 # We have to use this complex iteration strategy to allow multiple
3663 # iterations at the same time. We need to be able to catch revision
3664 # removed from _consumegen and added to genlist in another instance.
3665 #
3666 # Getting rid of it would provide an about 15% speed up on this
3667 # iteration.
3668 genlist = self._genlist
3669 nextrev = self._consumegen().next
3670 _len = len # cache global lookup
3671 def gen():
3672 i = 0
3673 while True:
3674 if i < _len(genlist):
3675 yield genlist[i]
3676 else:
3677 yield nextrev()
3678 i += 1
3679 return gen()
3680
3681 def _consumegen(self):
3682 cache = self._cache
3683 genlist = self._genlist.append
3684 for item in self._gen:
3685 cache[item] = True
3686 genlist(item)
3687 yield item
3688 if not self._finished:
3689 self._finished = True
3690 asc = self._genlist[:]
3691 asc.sort()
3692 self._asclist = asc
3693 self.fastasc = asc.__iter__
3694 self.fastdesc = asc.__reversed__
3695
3696 def __len__(self):
3697 for x in self._consumegen():
3698 pass
3699 return len(self._genlist)
3700
3701 def sort(self, reverse=False):
3702 self._ascending = not reverse
3703
3704 def reverse(self):
3705 self._ascending = not self._ascending
3706
3707 def isascending(self):
3708 return self._ascending
3709
3710 def isdescending(self):
3711 return not self._ascending
3712
3713 def istopo(self):
3714 # not worth the trouble asserting if the two sets combined are still
3715 # in topographical order. Use the sort() predicate to explicitly sort
3716 # again instead.
3717 return False
3718
3719 def first(self):
3720 if self._ascending:
3721 it = self.fastasc
3722 else:
3723 it = self.fastdesc
3724 if it is None:
3725 # we need to consume all and try again
3726 for x in self._consumegen():
3727 pass
3728 return self.first()
3729 return next(it(), None)
3730
3731 def last(self):
3732 if self._ascending:
3733 it = self.fastdesc
3734 else:
3735 it = self.fastasc
3736 if it is None:
3737 # we need to consume all and try again
3738 for x in self._consumegen():
3739 pass
3740 return self.first()
3741 return next(it(), None)
3742
3743 def __repr__(self):
3744 d = {False: '-', True: '+'}[self._ascending]
3745 return '<%s%s>' % (type(self).__name__, d)
3746
3747 class spanset(abstractsmartset):
3748 """Duck type for baseset class which represents a range of revisions and
3749 can work lazily and without having all the range in memory
3750
3751 Note that spanset(x, y) behave almost like xrange(x, y) except for two
3752 notable points:
3753 - when x < y it will be automatically descending,
3754 - revision filtered with this repoview will be skipped.
3755
3756 """
3757 def __init__(self, repo, start=0, end=None):
3758 """
3759 start: first revision included the set
3760 (default to 0)
3761 end: first revision excluded (last+1)
3762 (default to len(repo)
3763
3764 Spanset will be descending if `end` < `start`.
3765 """
3766 if end is None:
3767 end = len(repo)
3768 self._ascending = start <= end
3769 if not self._ascending:
3770 start, end = end + 1, start +1
3771 self._start = start
3772 self._end = end
3773 self._hiddenrevs = repo.changelog.filteredrevs
3774
3775 def sort(self, reverse=False):
3776 self._ascending = not reverse
3777
3778 def reverse(self):
3779 self._ascending = not self._ascending
3780
3781 def istopo(self):
3782 # not worth the trouble asserting if the two sets combined are still
3783 # in topographical order. Use the sort() predicate to explicitly sort
3784 # again instead.
3785 return False
3786
3787 def _iterfilter(self, iterrange):
3788 s = self._hiddenrevs
3789 for r in iterrange:
3790 if r not in s:
3791 yield r
3792
3793 def __iter__(self):
3794 if self._ascending:
3795 return self.fastasc()
3796 else:
3797 return self.fastdesc()
3798
3799 def fastasc(self):
3800 iterrange = xrange(self._start, self._end)
3801 if self._hiddenrevs:
3802 return self._iterfilter(iterrange)
3803 return iter(iterrange)
3804
3805 def fastdesc(self):
3806 iterrange = xrange(self._end - 1, self._start - 1, -1)
3807 if self._hiddenrevs:
3808 return self._iterfilter(iterrange)
3809 return iter(iterrange)
3810
3811 def __contains__(self, rev):
3812 hidden = self._hiddenrevs
3813 return ((self._start <= rev < self._end)
3814 and not (hidden and rev in hidden))
3815
3816 def __nonzero__(self):
3817 for r in self:
3818 return True
3819 return False
3820
3821 def __len__(self):
3822 if not self._hiddenrevs:
3823 return abs(self._end - self._start)
3824 else:
3825 count = 0
3826 start = self._start
3827 end = self._end
3828 for rev in self._hiddenrevs:
3829 if (end < rev <= start) or (start <= rev < end):
3830 count += 1
3831 return abs(self._end - self._start) - count
3832
3833 def isascending(self):
3834 return self._ascending
3835
3836 def isdescending(self):
3837 return not self._ascending
3838
3839 def first(self):
3840 if self._ascending:
3841 it = self.fastasc
3842 else:
3843 it = self.fastdesc
3844 for x in it():
3845 return x
3846 return None
3847
3848 def last(self):
3849 if self._ascending:
3850 it = self.fastdesc
3851 else:
3852 it = self.fastasc
3853 for x in it():
3854 return x
3855 return None
3856
3857 def __repr__(self):
3858 d = {False: '-', True: '+'}[self._ascending]
3859 return '<%s%s %d:%d>' % (type(self).__name__, d,
3860 self._start, self._end - 1)
3861
3862 class fullreposet(spanset):
3863 """a set containing all revisions in the repo
3864
3865 This class exists to host special optimization and magic to handle virtual
3866 revisions such as "null".
3867 """
3868
3869 def __init__(self, repo):
3870 super(fullreposet, self).__init__(repo)
3871
3872 def __and__(self, other):
3873 """As self contains the whole repo, all of the other set should also be
3874 in self. Therefore `self & other = other`.
3875
3876 This boldly assumes the other contains valid revs only.
3877 """
3878 # other not a smartset, make is so
3879 if not util.safehasattr(other, 'isascending'):
3880 # filter out hidden revision
3881 # (this boldly assumes all smartset are pure)
3882 #
3883 # `other` was used with "&", let's assume this is a set like
3884 # object.
3885 other = baseset(other - self._hiddenrevs)
3886
3887 other.sort(reverse=self.isdescending())
3888 return other
3889
3890 def prettyformatset(revs):
3891 lines = []
3892 rs = repr(revs)
3893 p = 0
3894 while p < len(rs):
3895 q = rs.find('<', p + 1)
3896 if q < 0:
3897 q = len(rs)
3898 l = rs.count('<', 0, p) - rs.count('>', 0, p)
3899 assert l >= 0
3900 lines.append((l, rs[p:q].rstrip()))
3901 p = q
3902 return '\n'.join(' ' * l + s for l, s in lines)
3903
3904 def loadpredicate(ui, extname, registrarobj): 2949 def loadpredicate(ui, extname, registrarobj):
3905 """Load revset predicates from specified registrarobj 2950 """Load revset predicates from specified registrarobj
3906 """ 2951 """
3907 for name, func in registrarobj._table.iteritems(): 2952 for name, func in registrarobj._table.iteritems():
3908 symbols[name] = func 2953 symbols[name] = func