mercurial/smartset.py
changeset 43076 2372284d9457
parent 40233 6309128ff61f
child 43077 687b865b95ad
equal deleted inserted replaced
43075:57875cf423c9 43076:2372284d9457
    11     encoding,
    11     encoding,
    12     error,
    12     error,
    13     pycompat,
    13     pycompat,
    14     util,
    14     util,
    15 )
    15 )
    16 from .utils import (
    16 from .utils import stringutil
    17     stringutil,
    17 
    18 )
       
    19 
    18 
    20 def _typename(o):
    19 def _typename(o):
    21     return pycompat.sysbytes(type(o).__name__).lstrip('_')
    20     return pycompat.sysbytes(type(o).__name__).lstrip('_')
    22 
    21 
       
    22 
    23 class abstractsmartset(object):
    23 class abstractsmartset(object):
    24 
       
    25     def __nonzero__(self):
    24     def __nonzero__(self):
    26         """True if the smartset is not empty"""
    25         """True if the smartset is not empty"""
    27         raise NotImplementedError()
    26         raise NotImplementedError()
    28 
    27 
    29     __bool__ = __nonzero__
    28     __bool__ = __nonzero__
   123     def __sub__(self, other):
   122     def __sub__(self, other):
   124         """Returns a new object with the substraction of the two collections.
   123         """Returns a new object with the substraction of the two collections.
   125 
   124 
   126         This is part of the mandatory API for smartset."""
   125         This is part of the mandatory API for smartset."""
   127         c = other.__contains__
   126         c = other.__contains__
   128         return self.filter(lambda r: not c(r), condrepr=('<not %r>', other),
   127         return self.filter(
   129                            cache=False)
   128             lambda r: not c(r), condrepr=('<not %r>', other), cache=False
       
   129         )
   130 
   130 
   131     def filter(self, condition, condrepr=None, cache=True):
   131     def filter(self, condition, condrepr=None, cache=True):
   132         """Returns this smartset filtered by condition as a new smartset.
   132         """Returns this smartset filtered by condition as a new smartset.
   133 
   133 
   134         `condition` is a callable which takes a revision number and returns a
   134         `condition` is a callable which takes a revision number and returns a
   161             if y is None:
   161             if y is None:
   162                 break
   162                 break
   163             ys.append(y)
   163             ys.append(y)
   164         return baseset(ys, datarepr=('slice=%d:%d %r', start, stop, self))
   164         return baseset(ys, datarepr=('slice=%d:%d %r', start, stop, self))
   165 
   165 
       
   166 
   166 class baseset(abstractsmartset):
   167 class baseset(abstractsmartset):
   167     """Basic data structure that represents a revset and contains the basic
   168     """Basic data structure that represents a revset and contains the basic
   168     operation that it should be able to perform.
   169     operation that it should be able to perform.
   169 
   170 
   170     Every method in this class should be implemented by any smartset class.
   171     Every method in this class should be implemented by any smartset class.
   220     >>> type(rs).__name__
   221     >>> type(rs).__name__
   221     'baseset'
   222     'baseset'
   222     >>> rs._istopo
   223     >>> rs._istopo
   223     True
   224     True
   224     """
   225     """
       
   226 
   225     def __init__(self, data=(), datarepr=None, istopo=False):
   227     def __init__(self, data=(), datarepr=None, istopo=False):
   226         """
   228         """
   227         datarepr: a tuple of (format, obj, ...), a function or an object that
   229         datarepr: a tuple of (format, obj, ...), a function or an object that
   228                   provides a printable representation of the given data.
   230                   provides a printable representation of the given data.
   229         """
   231         """
   340                 return self._asclist[0]
   342                 return self._asclist[0]
   341         return None
   343         return None
   342 
   344 
   343     def _fastsetop(self, other, op):
   345     def _fastsetop(self, other, op):
   344         # try to use native set operations as fast paths
   346         # try to use native set operations as fast paths
   345         if (type(other) is baseset and r'_set' in other.__dict__ and r'_set' in
   347         if (
   346             self.__dict__ and self._ascending is not None):
   348             type(other) is baseset
   347             s = baseset(data=getattr(self._set, op)(other._set),
   349             and r'_set' in other.__dict__
   348                         istopo=self._istopo)
   350             and r'_set' in self.__dict__
       
   351             and self._ascending is not None
       
   352         ):
       
   353             s = baseset(
       
   354                 data=getattr(self._set, op)(other._set), istopo=self._istopo
       
   355             )
   349             s._ascending = self._ascending
   356             s._ascending = self._ascending
   350         else:
   357         else:
   351             s = getattr(super(baseset, self), op)(other)
   358             s = getattr(super(baseset, self), op)(other)
   352         return s
   359         return s
   353 
   360 
   381             if self._ascending is not None:
   388             if self._ascending is not None:
   382                 l = self._asclist
   389                 l = self._asclist
   383             s = pycompat.byterepr(l)
   390             s = pycompat.byterepr(l)
   384         return '<%s%s %s>' % (_typename(self), d, s)
   391         return '<%s%s %s>' % (_typename(self), d, s)
   385 
   392 
       
   393 
   386 class filteredset(abstractsmartset):
   394 class filteredset(abstractsmartset):
   387     """Duck type for baseset class which iterates lazily over the revisions in
   395     """Duck type for baseset class which iterates lazily over the revisions in
   388     the subset and contains a function which tests for membership in the
   396     the subset and contains a function which tests for membership in the
   389     revset
   397     revset
   390     """
   398     """
       
   399 
   391     def __init__(self, subset, condition=lambda x: True, condrepr=None):
   400     def __init__(self, subset, condition=lambda x: True, condrepr=None):
   392         """
   401         """
   393         condition: a function that decide whether a revision in the subset
   402         condition: a function that decide whether a revision in the subset
   394                    belongs to the revset or not.
   403                    belongs to the revset or not.
   395         condrepr: a tuple of (format, obj, ...), a function or an object that
   404         condrepr: a tuple of (format, obj, ...), a function or an object that
   425             return None
   434             return None
   426         return lambda: self._iterfilter(it())
   435         return lambda: self._iterfilter(it())
   427 
   436 
   428     def __nonzero__(self):
   437     def __nonzero__(self):
   429         fast = None
   438         fast = None
   430         candidates = [self.fastasc if self.isascending() else None,
   439         candidates = [
   431                       self.fastdesc if self.isdescending() else None,
   440             self.fastasc if self.isascending() else None,
   432                       self.fastasc,
   441             self.fastdesc if self.isdescending() else None,
   433                       self.fastdesc]
   442             self.fastasc,
       
   443             self.fastdesc,
       
   444         ]
   434         for candidate in candidates:
   445         for candidate in candidates:
   435             if candidate is not None:
   446             if candidate is not None:
   436                 fast = candidate
   447                 fast = candidate
   437                 break
   448                 break
   438 
   449 
   482         elif self.isdescending():
   493         elif self.isdescending():
   483             it = self.fastasc
   494             it = self.fastasc
   484         if it is not None:
   495         if it is not None:
   485             for x in it():
   496             for x in it():
   486                 return x
   497                 return x
   487             return None #empty case
   498             return None  # empty case
   488         else:
   499         else:
   489             x = None
   500             x = None
   490             for x in self:
   501             for x in self:
   491                 pass
   502                 pass
   492             return x
   503             return x
   496         xs = [pycompat.byterepr(self._subset)]
   507         xs = [pycompat.byterepr(self._subset)]
   497         s = stringutil.buildrepr(self._condrepr)
   508         s = stringutil.buildrepr(self._condrepr)
   498         if s:
   509         if s:
   499             xs.append(s)
   510             xs.append(s)
   500         return '<%s %s>' % (_typename(self), ', '.join(xs))
   511         return '<%s %s>' % (_typename(self), ', '.join(xs))
       
   512 
   501 
   513 
   502 def _iterordered(ascending, iter1, iter2):
   514 def _iterordered(ascending, iter1, iter2):
   503     """produce an ordered iteration from two iterators with the same order
   515     """produce an ordered iteration from two iterators with the same order
   504 
   516 
   505     The ascending is used to indicated the iteration direction.
   517     The ascending is used to indicated the iteration direction.
   533             # might have been equality and both are empty
   545             # might have been equality and both are empty
   534             yield val2
   546             yield val2
   535         for val in it:
   547         for val in it:
   536             yield val
   548             yield val
   537 
   549 
       
   550 
   538 class addset(abstractsmartset):
   551 class addset(abstractsmartset):
   539     """Represent the addition of two sets
   552     """Represent the addition of two sets
   540 
   553 
   541     Wrapper structure for lazily adding two structures without losing much
   554     Wrapper structure for lazily adding two structures without losing much
   542     performance on the __contains__ method
   555     performance on the __contains__ method
   604     >>> rs = addset(generatorset(xs), ys, ascending=False)
   617     >>> rs = addset(generatorset(xs), ys, ascending=False)
   605     >>> assert rs.fastdesc is None
   618     >>> assert rs.fastdesc is None
   606     >>> [x for x in rs]
   619     >>> [x for x in rs]
   607     [5, 4, 3, 2, 0]
   620     [5, 4, 3, 2, 0]
   608     """
   621     """
       
   622 
   609     def __init__(self, revs1, revs2, ascending=None):
   623     def __init__(self, revs1, revs2, ascending=None):
   610         self._r1 = revs1
   624         self._r1 = revs1
   611         self._r2 = revs2
   625         self._r2 = revs2
   612         self._iter = None
   626         self._iter = None
   613         self._ascending = ascending
   627         self._ascending = ascending
   639         same time, yielding only one value at a time in the given order.
   653         same time, yielding only one value at a time in the given order.
   640         """
   654         """
   641         if self._ascending is None:
   655         if self._ascending is None:
   642             if self._genlist:
   656             if self._genlist:
   643                 return iter(self._genlist)
   657                 return iter(self._genlist)
       
   658 
   644             def arbitraryordergen():
   659             def arbitraryordergen():
   645                 for r in self._r1:
   660                 for r in self._r1:
   646                     yield r
   661                     yield r
   647                 inr1 = self._r1.__contains__
   662                 inr1 = self._r1.__contains__
   648                 for r in self._r2:
   663                 for r in self._r2:
   649                     if not inr1(r):
   664                     if not inr1(r):
   650                         yield r
   665                         yield r
       
   666 
   651             return arbitraryordergen()
   667             return arbitraryordergen()
   652         # try to use our own fast iterator if it exists
   668         # try to use our own fast iterator if it exists
   653         self._trysetasclist()
   669         self._trysetasclist()
   654         if self._ascending:
   670         if self._ascending:
   655             attr = 'fastasc'
   671             attr = 'fastasc'
   745     @encoding.strmethod
   761     @encoding.strmethod
   746     def __repr__(self):
   762     def __repr__(self):
   747         d = {None: '', False: '-', True: '+'}[self._ascending]
   763         d = {None: '', False: '-', True: '+'}[self._ascending]
   748         return '<%s%s %r, %r>' % (_typename(self), d, self._r1, self._r2)
   764         return '<%s%s %r, %r>' % (_typename(self), d, self._r1, self._r2)
   749 
   765 
       
   766 
   750 class generatorset(abstractsmartset):
   767 class generatorset(abstractsmartset):
   751     """Wrap a generator for lazy iteration
   768     """Wrap a generator for lazy iteration
   752 
   769 
   753     Wrapper structure for generators that provides lazy membership and can
   770     Wrapper structure for generators that provides lazy membership and can
   754     be iterated more than once.
   771     be iterated more than once.
   758     >>> xs = generatorset([0, 1, 4], iterasc=True)
   775     >>> xs = generatorset([0, 1, 4], iterasc=True)
   759     >>> assert xs.last() == xs.last()
   776     >>> assert xs.last() == xs.last()
   760     >>> xs.last()  # cached
   777     >>> xs.last()  # cached
   761     4
   778     4
   762     """
   779     """
       
   780 
   763     def __new__(cls, gen, iterasc=None):
   781     def __new__(cls, gen, iterasc=None):
   764         if iterasc is None:
   782         if iterasc is None:
   765             typ = cls
   783             typ = cls
   766         elif iterasc:
   784         elif iterasc:
   767             typ = _generatorsetasc
   785             typ = _generatorsetasc
   828         #
   846         #
   829         # Getting rid of it would provide an about 15% speed up on this
   847         # Getting rid of it would provide an about 15% speed up on this
   830         # iteration.
   848         # iteration.
   831         genlist = self._genlist
   849         genlist = self._genlist
   832         nextgen = self._consumegen()
   850         nextgen = self._consumegen()
   833         _len, _next = len, next # cache global lookup
   851         _len, _next = len, next  # cache global lookup
       
   852 
   834         def gen():
   853         def gen():
   835             i = 0
   854             i = 0
   836             while True:
   855             while True:
   837                 if i < _len(genlist):
   856                 if i < _len(genlist):
   838                     yield genlist[i]
   857                     yield genlist[i]
   840                     try:
   859                     try:
   841                         yield _next(nextgen)
   860                         yield _next(nextgen)
   842                     except StopIteration:
   861                     except StopIteration:
   843                         return
   862                         return
   844                 i += 1
   863                 i += 1
       
   864 
   845         return gen()
   865         return gen()
   846 
   866 
   847     def _consumegen(self):
   867     def _consumegen(self):
   848         cache = self._cache
   868         cache = self._cache
   849         genlist = self._genlist.append
   869         genlist = self._genlist.append
   909     @encoding.strmethod
   929     @encoding.strmethod
   910     def __repr__(self):
   930     def __repr__(self):
   911         d = {False: '-', True: '+'}[self._ascending]
   931         d = {False: '-', True: '+'}[self._ascending]
   912         return '<%s%s>' % (_typename(self), d)
   932         return '<%s%s>' % (_typename(self), d)
   913 
   933 
       
   934 
   914 class _generatorsetasc(generatorset):
   935 class _generatorsetasc(generatorset):
   915     """Special case of generatorset optimized for ascending generators."""
   936     """Special case of generatorset optimized for ascending generators."""
   916 
   937 
   917     fastasc = generatorset._iterator
   938     fastasc = generatorset._iterator
   918 
   939 
   928                 break
   949                 break
   929 
   950 
   930         self._cache[x] = False
   951         self._cache[x] = False
   931         return False
   952         return False
   932 
   953 
       
   954 
   933 class _generatorsetdesc(generatorset):
   955 class _generatorsetdesc(generatorset):
   934     """Special case of generatorset optimized for descending generators."""
   956     """Special case of generatorset optimized for descending generators."""
   935 
   957 
   936     fastdesc = generatorset._iterator
   958     fastdesc = generatorset._iterator
   937 
   959 
   946             if l < x:
   968             if l < x:
   947                 break
   969                 break
   948 
   970 
   949         self._cache[x] = False
   971         self._cache[x] = False
   950         return False
   972         return False
       
   973 
   951 
   974 
   952 def spanset(repo, start=0, end=None):
   975 def spanset(repo, start=0, end=None):
   953     """Create a spanset that represents a range of repository revisions
   976     """Create a spanset that represents a range of repository revisions
   954 
   977 
   955     start: first revision included the set (default to 0)
   978     start: first revision included the set (default to 0)
   962     ascending = start <= end
   985     ascending = start <= end
   963     if not ascending:
   986     if not ascending:
   964         start, end = end + 1, start + 1
   987         start, end = end + 1, start + 1
   965     return _spanset(start, end, ascending, repo.changelog.filteredrevs)
   988     return _spanset(start, end, ascending, repo.changelog.filteredrevs)
   966 
   989 
       
   990 
   967 class _spanset(abstractsmartset):
   991 class _spanset(abstractsmartset):
   968     """Duck type for baseset class which represents a range of revisions and
   992     """Duck type for baseset class which represents a range of revisions and
   969     can work lazily and without having all the range in memory
   993     can work lazily and without having all the range in memory
   970 
   994 
   971     Note that spanset(x, y) behave almost like xrange(x, y) except for two
   995     Note that spanset(x, y) behave almost like xrange(x, y) except for two
   972     notable points:
   996     notable points:
   973     - when x < y it will be automatically descending,
   997     - when x < y it will be automatically descending,
   974     - revision filtered with this repoview will be skipped.
   998     - revision filtered with this repoview will be skipped.
   975 
   999 
   976     """
  1000     """
       
  1001 
   977     def __init__(self, start, end, ascending, hiddenrevs):
  1002     def __init__(self, start, end, ascending, hiddenrevs):
   978         self._start = start
  1003         self._start = start
   979         self._end = end
  1004         self._end = end
   980         self._ascending = ascending
  1005         self._ascending = ascending
   981         self._hiddenrevs = hiddenrevs
  1006         self._hiddenrevs = hiddenrevs
  1016             return self._iterfilter(iterrange)
  1041             return self._iterfilter(iterrange)
  1017         return iter(iterrange)
  1042         return iter(iterrange)
  1018 
  1043 
  1019     def __contains__(self, rev):
  1044     def __contains__(self, rev):
  1020         hidden = self._hiddenrevs
  1045         hidden = self._hiddenrevs
  1021         return ((self._start <= rev < self._end)
  1046         return (self._start <= rev < self._end) and not (
  1022                 and not (hidden and rev in hidden))
  1047             hidden and rev in hidden
       
  1048         )
  1023 
  1049 
  1024     def __nonzero__(self):
  1050     def __nonzero__(self):
  1025         for r in self:
  1051         for r in self:
  1026             return True
  1052             return True
  1027         return False
  1053         return False
  1079     @encoding.strmethod
  1105     @encoding.strmethod
  1080     def __repr__(self):
  1106     def __repr__(self):
  1081         d = {False: '-', True: '+'}[self._ascending]
  1107         d = {False: '-', True: '+'}[self._ascending]
  1082         return '<%s%s %d:%d>' % (_typename(self), d, self._start, self._end)
  1108         return '<%s%s %d:%d>' % (_typename(self), d, self._start, self._end)
  1083 
  1109 
       
  1110 
  1084 class fullreposet(_spanset):
  1111 class fullreposet(_spanset):
  1085     """a set containing all revisions in the repo
  1112     """a set containing all revisions in the repo
  1086 
  1113 
  1087     This class exists to host special optimization and magic to handle virtual
  1114     This class exists to host special optimization and magic to handle virtual
  1088     revisions such as "null".
  1115     revisions such as "null".
  1089     """
  1116     """
  1090 
  1117 
  1091     def __init__(self, repo):
  1118     def __init__(self, repo):
  1092         super(fullreposet, self).__init__(0, len(repo), True,
  1119         super(fullreposet, self).__init__(
  1093                                           repo.changelog.filteredrevs)
  1120             0, len(repo), True, repo.changelog.filteredrevs
       
  1121         )
  1094 
  1122 
  1095     def __and__(self, other):
  1123     def __and__(self, other):
  1096         """As self contains the whole repo, all of the other set should also be
  1124         """As self contains the whole repo, all of the other set should also be
  1097         in self. Therefore `self & other = other`.
  1125         in self. Therefore `self & other = other`.
  1098 
  1126